Variablen und Datentypen


Processing 2.0

Alle Daten im Computer werden irgendwann einmal in eine Folge 0er und 1er übersetzt. In Software sind Daten meist Buchstaben und Zahlen, aber auch Bilder, Musik und Video. In Processing kann man viele dieser verschiedenen Daten verarbeiten, aber nicht alle auf die gleiche Weise.

Datentypen

Sie legen fest, wie Daten in die Sprache des Computers (eine Reihe aus 0 und 1) übersetzt werden. Es gibt sog. primitive Datentypen, die in den meisten Programmiersprachen schon festgelegt sind. Darüber hinaus, kann man aber auch seine eigenen Datentypen erstellen.

Primitive Datentypen sind:

  • int – 32 bit – Ganzzahlen von ca. -2 Mrd. bis ca. +2Mrd.
  • float – 32 bit – Gleitkommazahl mit 8 Stellen Genauigkeit
  • boolean – 1 bit – true oder false
  • byte – 8 bit – -128 bis+127
  • char – 16 bit  – 0 bis 65535 für einzelne Zeichen
  • color – 32 bit – 1.6777.216 Farben

Im Gegensatz zu den primitiven gibt es auch nicht primitive Datentypen (z.b. String), die bei Bedarf auch selbst definiert werden können. Beim Typ String kann man schon an der Schreibweise (groß) erkennen, dass er sich von den primitiven Datentypen unterscheidet. Und zwar dadurch, dass er, wie auch in Java üblich ein Objekt ist, dessen Eigenschaften durch eine Klasse definiert werden (zur genaueren Erklärung suche nach Objekte und Klassen). Schauen wir uns nun den Unterschied zwischen char und String genauer an. Während eine Variable vom Typ char ein Zeichen enthalten kann, ist kann ein String eine beliebige Anzahl an Zeichen (eine Zeichenkette) beinhalten. Ein weiterer Unterschied besteht in der Zuweisung der Werte.

char zeichen = ‚a‘; // deklariert die Variable zeichen und weist ihr den Wert a zu
char zeichen = „a“; // Error! ist nicht zulässig

String kette = „hallo welt!“; //Erzeugt ein Objekt vom Typ String mit dem Namen kette und weist diesem Objekt den Wert „hallo welt“ zu.

Variablen

Variablen sind Platzhalter im Speicher und können mit entsprechenden Werten beschrieben werden. Bevor ein Wert in einer Variablen gespeichert werden kann, muss diese „deklariert“ werden. Dabei wird ein entsprechender Datentyp für die Variable definiert. Bsp:

  • int x; // deklariert die Variable x
  • x=12; //weist der Variablen x den Wert 12 zu
  • float z; //deklariert die Variable z
  • z=3,14 //weist der Variablen z den Wert 3,14 zu
  • boolean wahr;//deklariert die Variable wahr
  • wahr=true;//weist der Variablen wahr den Wert true zu

Man kann das Ganze etwas abkürzen und Deklaration und Wertzuweisung in einem Schritt durchführen.

  • int x=12;
  • float z=3,14;

Eine Variable darf nur einmal deklariert werden, kann dann aber nacheinander verschiedenste Werte enthalten.

Variablenname dürfen frei gewählt werden, allerdings keine reservierten Ausdrücke sein (int, null, true, false usw.).

Processing hat einige vordefinierte Variablen, nämlich in  size(): width and height. Wenn diese nicht anders definiert werden, haben sie den Wert 100. Deshalb ist das Anwendungsfenster in einem Programm ohne size(x,y) – Anweisung 100*100 px groß.

 println(width + ", " + height);
//gibt 100, 100 im Terminalfenster aus

Wenn man ein Programm für verschiedene Auflösungen schreiben will, ist es gut die Variablen width und height zu verwenden, da man dann allein mit der size() – Funktion die ganze Anwendung skalieren kann.

Beispiel:

size(300,300);
rect(width/10,height/10,width/2,height/2);
rect(width/4,height/4,width/2,height/2);
ellipse(width/2,height/2,width/4, height/4);

Aufgabe: Schreibe ein Programm, das einige Variablen von den Typen char und String erzeugt und gibt diese mit der Funktion println im Konsolenfenster aus.

Mathematische Operationen


Processing 2.0

Da alles was in einer Anwendung gezeigt wird auf Nummern basiert, ist es unerlässlich, dass wir uns zumindest kurz ausschließlich mit diesen beschäftigen.

Beispiel: starte Applet

Image

size(250,250);
int grau=50;
fill(grau);
rect(20,20,100,100);
grau=grau+50;
fill(grau);
rect(40,40,100,100);
grau=grau+50;
fill(grau);
rect(60,60,100,100);
grau=grau+50;
fill(grau);
rect(80,80,100,100);
grau=grau+50;
fill(grau);
rect(100,100,100,100);

Hier wird mit Hilfe einer Variable der Grau-Wert der Quadrate definiert.

Beispiel: starte Applet

Image

size(300,300);
int x=2; //x ist jetzt 2
line(x,0, x,height);
x=x*2;//x ist jetzt 4
line(x,0, x,height);
x=x*2;//x ist jetzt 8
line(x,0, x,height);
x=x*2;//x ist jetzt 16
line(x,0, x,height);
x=x*2;//x ist jetzt 32
line(x,0, x,height);
x=x*2;//x ist jetzt 64
line(x,0, x,height);
x=x*2;//x ist jetzt 128
line(x,0, x,height);
x=x*2;//x ist jetzt 256
line(x,0, x,height);

Hier werden Parameter der Funktion line() durch die Variable x kontrolliert.

Übung: Verändere das Programm Variablen so, dass es horizontale statt  vertikale Linien zeichnet und lass die Linien immer um 2 Pixel dicker werden.

Modulo

In einem Programm kann man also ganz leicht alle Grundrechnungsarten anwenden. Oft ist es aber auch nötig den Rest einer Berechnung mit Ganzzahlen zu ermitteln. Dafür gibt es einen eigenen Operator. Dieser wird Modulo genannt und mit einem % geschrieben.

10 % 3 = 1
6 % 2 = 0
29 % 9 = 2

Natürlich können solche Berechnungen nur mit Zahlen des Datentyps int durchgefürht werden.

Datentypen können nicht beliebig kombiniert werden. Ein Ausdruck in der Form int x=4.0/3 liefert eine Fehlermeldung. float x=4.0/3 würde hingegen 1,3333334 ergeben. Die gleichen Ergebnisse kommen auch zustande, wenn die Zahlen durch Variablen mit den gleichen Werten ersetzt werden. Man muss also eigentlich immer genau wissen, welche Werte eine Variable annehmen kann.

Punktrechnung vor Strichrechnung

… gilt auch in Processing. Zudem kann man, wie auch in der Mathematik, Klammern setzen.

In der Kürze liegt die Würze

x++ steht für x=x+1;
y– für y=y-1;
x+=5 für x=x+5;
y-=5 für y=y-5;

usw.

Mathematische Funktionen

ceil();

Berechnet die nächste Ganzzahl aus einer Gleitkommazahl und rundet dabei immer auf.

floor();

Berechnet die nächste Ganzzahl aus einer Gleitkommazahl und rundet dabei immer ab.

round();

Berechnet eine Ganzzahl aus einer Gleitkommazahl. Dabei wird gerundet.

min(); und max();

Geben von beliebig vielen Zahlen als Parameter jeweils die größte oder die kleinste zurück.

Werte Normalisieren und Mappen

norm(Wert, niedrig, hoch);

Beim Normalisieren geht es darum Wertbereiche ineinander Umzuwandeln, bsp. einen Farbwinkel(0-360°) in einen Bereich zwischen 0 und 1. Die entsprechende Anweisung  würde dann lauten: norm(Winkelx, 0, 360);

Der Vorteil des Zahlenbereichs zwischen 0 und 1 besteht darin, dass man die Zahlen beliebig multiplizieren und dividieren kann, ohne je den Bereich zu verlassen.

lerp(niedrig, hoch, norm.Wert);

Die lerp()-Funktion ist die Umkehrfunktion von norm(). Man gibt einen Bereich an und dann einen bereits normalisierten Wert (zwischen 0 und 1). Die Funktion gibt dann den, dem Bereich entsprechenden Wert zurück.

map(Wert, niedrig1, hoch1, niedrig2, hoch2);

Mit der Funtion map() kann man zwei beliebige Zahlenbereiche direkt ineinander umwandeln.

Mouse Input


Processing 2.0

Position des Mauszeigers

Natürlich kann Processing auch auf Benutzereingaben reagieren.  Die Variablen mouseX und mouseY geben die aktuellen x- und y- Koordinaten des Mauszeigers zurück. Zu Beginn sind die beiden 0, das bleiben sie auch, wenn die draw()– Funktion nicht aufgerufen, oder mit noLoop() nur ein mal ausgeführt wird.

Beispiel: starte Applet

Image

void setup() {
size(300,300);
noStroke();
}
void draw() {
background(50);
ellipse(mouseX,mouseY,30,30);
}

Übung: Erstelle ein Programm, in dem man mit einem weißen Stift einen dunklen Hintergrund anmalen kann.

Um festzustellen, ob die Maus sich bewegt hat, kann man die Variablen pmouseX (für previous) und pmouseY verwenden. Sie haben den Wert des Mauszeigers des vorangegangenen Frames. Wenn die Maus sich nicht bewegt sind also mouseX und pmouseX gleich. Bewegt sich die Maus, sind sie unterschiedlich.

Beispiel: starte Applet

Image

</pre>
void setup() {
size(300,300);
strokeWeight(12);
}
void draw() {
background(50);
stroke(255);
line(mouseX,mouseY,pmouseX,pmouseY);
}

Übung: Mit der Funktion frameRate() kann man festlegen, wie schnell der Bildschirminhalt erneuert wird. Wert steht für Bilder/sek. Probiere das Programm mit verschiedenen FrameRate – Werten aus.

Mausbuttons

Dafür gibt es in Processing die Variable mousePressed, die bei gedrückem Mausbutton true, sonst false rückmeldet. Außerdem ist es möglich bis zu 3 Mausbuttons abzufragen. Das geht mit mouseButton und liefert die Werte LEFT, CENTER oder RIGHT.

Beispiel: starte Applet

Achtung: funktioniert nicht im Android Modus!

Image

void setup() {
size(300,300);
}
void draw() {
background(200);
if (mousePressed) {
if (mouseButton == LEFT) {
fill(0);
} else if (mouseButton == RIGHT) {
fill(255);
} else {
fill(120);
}
ellipse(100,100,100,100);
}
}

Mauszeiger

Mit den Funktionen noCursor() und cursor() kann der Mauszeiger ein- und ausgeblendet werden. Ein individueller Mauszeiger kann mit Hilfe eines beliebigen Objekts und der Variablen mouseX und mouseY als Koordinatenangabe erstellt werden.

Aufgabe: Verändere das obige Programm so, dass es den Mauszeiger nicht mehr anzeigt.

Aufgabe: Schreibe ein Programm, das den Mauszeiger anzeigt und die Maus gleichzeitig ein Objekt bewegt, das aber in X und Y-Richtung nur immer 1/3 des Weges des Mauszeigers zurücklegt.


if – Anweisung


Processing 2.0

Bis jetzt sind unsere Programme immer von vorne nach hinten durchgelaufen, ohne auf irgend etwas zu reagieren.

Die if – Anweisung gibt uns die Möglichkeit Bedingungen für den weiteren Verlauf des Programms zu setzen. Das können true/false -Abfragen oder der Vergleich zweier Zahlen sein. Es sollten bei Zahlen immer nur Ganzzahlen (int) miteinander verglichen werden.

Vergleichsoperatoren:

>      größer als
<      kleiner als
>=   größer oder gleich
<=   kleiner oder gleich
==   ist gleich (als Vergleich, einfaches = ist ein Zuweisungsoperator!!!)
!=    ist nicht gleich
||     logisches oder
&&  logisches und
!      logisches nicht.

Für Anweisungen, die nur ausgeführt werden sollen, wenn bestimmte Bedingungen erfüllt sind, empfiehlt sich folgende Schreibweise:

if (test) {
Anweisungen
}

Wobei test für die Bedingung steht.

Beispiel: starte Applet

Image

void setup(){
size(300,300);
}
void draw(){
background(200);
stroke(5);
fill(150);
if (mouseX<150 && mouseY<150){
rect(0,0,150,150);
}
else if (mouseX>150 && mouseY<150){
rect(150,0,150,150);
}
else if (mouseX<150 && mouseY>150){
rect(0,150,150,150);
}
else if (mouseX>150 && mouseY>150){
rect(150,150,150,150);
}
}

Als Struktogramm sieht das Programm so aus:

Image

Viele Möglichkeiten ergeben sich dann,wenn man versucht mehrere Bedingungen ineinander zu verschachteln.

Logische Operationen

In Processing kann man 3 logische Operationen verwenden (&& logisches UND, || logisches ODER und ! logisches NICHT).

Diese Operationen kann man nun beliebig miteinander verknüpfen. Man nennt dies die Boolsche Algebra.

Schleifen


Processing 2.0

Mit Schleifen kann man unter anderem den Programmablauf steuern. Wir werden uns jetzt speziell mit der for-Schleife beschäftigen. Damit kann man den zu schreibenden Code beträchtlich verkürzen, was zu weniger Fehlern und besserem Überblick führt.

Beispiel (lang, ohne Schleife): starte Applet

Image

size (300,300);
line (10,10,190,10);
line (10,20,190,20);
line (10,30,190,30);
line (10,40,190,40);
line (10,50,190,50);
line (10,60,190,60);
line (10,70,190,70);
line (10,80,190,80);
line (10,90,190,90);
line (10,100,190,100);
line (10,110,190,110);
line (10,120,190,120);
line (10,130,190,130);
line (10,140,190,140);
line (10,150,190,150);
line (10,160,190,160);
line (10,170,190,170);
line (10,180,190,180);
line (10,190,190,190);

Beispiel (kurzer Code mit Schleife): starte Applet

Image

size (300,300);
for (int i=10; i<width; i+=10) {
line (10,i,290,i);
}

Die Länge des Codes kann also in diesem Beispiel von 20 Zeilen auf 4 Zeilen verringert werden, ohne die Funktionsweise zu verändern.

Aufgabe: Schreibe ein Programm, das vom Mittelpunkt ausgehend immer größere Kreise auf den Schirm zeichnet. Das ganze sollte so ausshen:  Image

Tipp: verwende noFill(), um nur den Umriss der Ellipsen zu zeigen.

Es gibt nun die Möglichkeit, in Schleifen entweder Bedingungen zu verwenden, oder sie zu verschachteln.

Beispiel: starte Applet

Image

size (300,300);
noFill(); //nur Umriss wird gezeigt
for (int i=10; i<width; i+=10) {
if (i%20==0){ //bleibt kein Rest bei der Division durch 20 --> Kreis
ellipse (width/2,height/2,i,i);
} else { //sonst --> Ellipse
ellipse (width/2,height/2,i+10,i);
}
}

Beispiel: starte Applet

Image

size (300,300);
fill(127);
for (int x=10; x<width; x+=10) {
  for (int y=10; y<height; y+=10) {
    ellipse (x,y, 10, 10);
  }
} 

Hier werden für die x- und die y-Richtung je eine Schleife verwendet und Ellipsen mit 10 Pixel Radius auf den Schirm gezeichnet.

Aufgabe: Verändere das Programm so, dass die Größe der gezeichneten Ellipsen durch die Position der Maus veränder werden kann. Applet

Image

Neben der for-Schleife gibt es auch noch die Möglichkeit einer while- Schleife. Sie wird vorwiegend in Fällen eingesetzt, in denen man von vorne herein nicht weiß, wie viele Schleifendurchläufe es geben soll.

Die while -Schleife wird durchlaufen, solange die jeweilig Bedingung erfüllt ist.

while (i>10)  {
Anweisungen
}

Projekt Bild aus Text


Processing 2.0

Dieses Programm liest ein Bild ein und bildet es aus einem frei wählbaren Text neu. Dabei wird die Größe der Buchstaben von der Helligkeit der Bildstelle bestimmt und der Buchstabe in der Farbe der Bildstelle gezeichnet. Dies kann aber, so wie auch die Max- und Minimalgröße der Buchstaben zur Laufzeit des Programms geändert werden.

Wie das funktioniert, ist in den Kommentaren beschrieben.

Beispiel: Das Henne-Ei-Problem

ImageImage

// Generative Gestaltung, ISBN: 978-3-87439-759-9
// First Edition, Hermann Schmidt, Mainz, 2009
// Hartmut Bohnacker, Benedikt Groß, Julia Laub, Claudius Lazzeroni
// Copyright 2009 Hartmut Bohnacker, Benedikt Groß, Julia Laub, Claudius Lazzeroni
//
// modified by 2010 Thomas Koberger
//
// http://www.generative-gestaltung.de
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* pixel mapping. each pixel is translated into a new element (letter)
*
* KEYS
* 1                 : toogle font size mode (dynamic/static)
* 2                 : toogle font color mode (color/b&w)
* arrow up/down     : maximal fontsize +/-
* arrow right/left  : minimal fontsize +/-
* s                 : save png
* p                 : save pdf
*/

import processing.pdf.*;
import java.util.*;
boolean savePDF = false;

String inputText ="Henne"; //modified
float fontSizeMax = 50;
float fontSizeMin = 14;
float spacing = 20; // Zeilenabstand
float kerning = 0.5; // Abstand zwischen den Buchstaben

boolean fontSizeStatic = false;
boolean blackAndWhite = false;

PFont font;
PImage img;
int textstellen=0;//darin wird gespeichert, wie oft der Text ausgegeben wurde

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

//falls es Probleme mit der Darstellung der richtigen Schrift
//geben sollte, kann man die Schrift auch von Hand erstellen und
//dann aus dem Programmordner laden (wie im Artikel Text beschrieben!

font = createFont("Impact",60); //modified
img = loadImage("ei.jpg");//modified
println(img.width+" x "+img.height);
}

void draw() {
background(255);
//ermöglicht das speichern als *.pdf
if (savePDF) beginRecord(PDF, timestamp()+".pdf");

textAlign(LEFT);
//textAlign(LEFT,CENTER); //// also nice!

float x = 0, y = 0; //sorgt dafür, dass ein 100 Pixel breiter Rand frei  bleibt
int counter = 0;

while (y < height-0) {
// translate position (display) to position (image)
// die Zahl 100 steht für 100 Pixel, die als Rand freigelassen werden.
int imgX = (int) map(x, 0,width, 0,img.width);
int imgY = (int) map(y, 0,height, 0,img.height);
// get current color
color c = img.pixels[imgY*img.width+imgX];
//wandelt eine Farbe in einen Grauwert um
int greyscale = round(red(c)*0.222 + green(c)*0.707 + blue(c)*0.071);
// speichert die Position des Koordinatensystems
pushMatrix();
//verschiebt das Koordinatensystem um x und y
translate(x, y);

if (fontSizeStatic) {
textFont(font, fontSizeMax);
if (blackAndWhite) fill(greyscale);
else fill(c);
}
else {
// das ist die Standardeinstellung zu Beginn des Programms
// greyscale to fontsize
float fontSize = map(greyscale, 0,255, fontSizeMax,fontSizeMin);
fontSize = max(fontSize, 1);
textFont(font, fontSize);
if (blackAndWhite) fill(0);
else fill(c);
}
//hier wird der jeweils nächste Buchstabe in die Variable letter geschrieben
char letter = inputText.charAt(counter);
//gibt den Text am Bildschirm aus
text(letter, 0, 0);
float letterWidth = textWidth(letter) + kerning;
// for the next letter ... x + letter width
x = x + letterWidth; // update x-coordinate
popMatrix();

// linebreaks
if (x+letterWidth >= width-100) {
x = 100;
y = y + spacing; // add line height
}

counter++;
// damit der Text wiederholt ausgegeben wird
if (counter > inputText.length()-1) {
textstellen++;
counter = 0;
}
}

if (savePDF) {
savePDF = false;
endRecord();
}
println(textstellen);
}


void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
//if (key == 'p' || key == 'P') savePDF = true;
// change render mode
if (key == '1') fontSizeStatic = !fontSizeStatic;
// change color stlye
if (key == '2') blackAndWhite = !blackAndWhite;
println("fontSizeMin: "+fontSizeMin+"  fontSizeMax: "+fontSizeMax+"   fontSizeStatic: "+fontSizeStatic+"   blackAndWhite: "+blackAndWhite);
}

void keyPressed() {
// change fontSizeMax with arrowkeys up/down
if (keyCode == UP) fontSizeMax += 2;
if (keyCode == DOWN) fontSizeMax -= 2;
// change fontSizeMin with arrowkeys left/right
if (keyCode == RIGHT) fontSizeMin += 2;
if (keyCode == LEFT) fontSizeMin -= 2;

//fontSizeMin = max(fontSizeMin, 2);
//fontSizeMax = max(fontSizeMax, 2);
}

// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}

Textausgabe


Processing 2.0

Bevor man Text in Processing ausgeben kann, muss man eine beliebige Schrift zuerst in das .vlw Format konvertieren (Processing –> Tools –> Create Font …).

Bei diesem Vorgang werden der oder die Buchstaben in der angegebenen Größe gerendert. D.h. sei liegen dann als Rastergrafik vor, was zur Folge hat, dass sie nicht mehr beliebig skalierbar sind, sonder man vorher überlegen muss, wie groß die Zeichen sein müssen. Die Rastergrafiken werden in einer .vlw – Datei im Sketch- Ordner des aktuellen Projektes gespeichert.

Alternativ dazu kann man auch im Programm eine Schrift erzeugen, dabei muss man beachten, dass auf einem anderen System die entsprechende Schrift vielleicht nicht installiert ist und sich daraus dann Probleme ergeben. Um Probleme auszuschließen kann mit der Methode Pfont.list() ein Array mit den, auf dem System verfügbaren Schriften erzeugen. Die Anweisung würde dann so aussehen:

String [] fontList=PFont.list();
font = createFont(„Miso“,12);

Im Android Mode sollte man aus Gründen der Effizienz immer die createFont() Funktion nutzen!

Um im Programm auf die Zeichen zugreifen zu können, gibt es einen eigenen Datentyp, nämlich PFont. Um Text auf dem Bildschirm ausgeben zu können, muss man eine Variable (genau genommen ein Objekt) vom Typ PFont erstellen und dann mit der Funktion LoadFont() die Zeichen laden. Mit textFont() muss die aktuelle Schrift ausgewählt werden. Um den Text dann anzeigen zu können benutzt man die Funktion text().

text(daten, x,y);
text(daten, x,y, breite, höhe);

Wobei daten alle Daten von den Typen String, char, int, float enthalten kann.

textSize();

Die Funktion textSize() legt die Schriftgröße fest. Wenn aber die gewählte Schriftgröße größer ist, als der Wert, mit dem die Schrift gerendert wurde, dann wird sie pixelig!

Beispiel: starte Applet

Image

PFont font; //erstellt ein Pfont-Objekt mit dem Namen font

size(300,300);
font = loadFont("ArialMT-48.vlw"); //weist dem Objekt font die Grafiken für die einzelnen Buchstaben zu
textFont(font); //estellt ein Objekt textFont mit dem Parameter font

textSize(240);//setzt die Schriftgröße auf 180 Pixel
fill(255); //legt die Schriftfarbe fest
text(2, 80, 240); //gibt text auf dem Schirm aus
textSize(36);
fill(0);
text("lorem ipsum ...",20,70); //gibt text auf dem Schirm aus

Vorsicht: Bei exotischen Schriften stehen oft nicht alle Zeichen zur Verfügung (Zahlen und Umlaute).

Beispiel: starte Applet

Image

PFont font; //erstellt ein Pfont-Objekt mit dem Namen font

size(300,300);
background(255);
font = loadFont("ArialMT-48.vlw"); //weist dem Objekt font die Grafiken für die einzelnen Buchstaben zu
textFont(font); //estellt ein Objekt textFont mit dem Parameter font
//setzt die Schriftgröße auf 180 Pixel
for (int i=1; i<10; i++) {
fill(0,220-i*20);
textSize(20+i*10);
text(i, i*30-30, 150); //gibt text auf dem Schirm aus
}

textLeading(abstand);

Abstand ist dabei der Abstand der Zeilen in Pixel.

textWidth(float);

Mit der Funktion textWidth() kann man die genaue Breite eines Strings (in Pixel) ermitteln.

textAlign(x-Richtung, y-Richtung);

Mit textAlign kann man die ein Textobjekt ausrichten, wie man das von der Textverarbeitung gewohnt ist. In der horizontalen x-Richtung sind die Parameter LEFT, CENTER und RIGHT möglich. In vertikaler Richtung TOP, BOTTOM, CENTER, und BASELINE.

Einen Zeilenumbruch im Fließtext erreicht man mit \n.

Aufgabe: Schreibe ein Programm, das deinen Nachnamen schön formatiert am Bildschirm ausgibt.

Aufgabe: Überlege Dir, wie man Buchstaben in Kreisform anordnen könnte! Versuche daraus ein Programm zu erstellen!

Tastatureingaben


Processing 2.0

Die einfachste Methode abzufragen, ob eine Taste auf der Tastatur gedrückt wurde, stellt die Variable keyPressed dar. Sie kann die Werte true oder false annehmen.

Beispiel: starte Applet

Image


int x = 10;

void setup() {
  size(300,300);
}
void draw() {
  background(200);
  if (keyPressed == true) {
    x=x+1;
  }
  ellipse(x,100,10,10);
}

Drücke irgend eine Taste, um die Ellipse von links nach rechts zu bewegen. Im Android Mode muss man die virtuelle Tastatur aktivieren!

Die Variable key vom Typ char speichert jeweils die letzte Tastatureingabe.

Beispiel: starte Applet

Achtung: Die Grafiken für die Buchstaben müssen vor der Benutzung mit Processing –> Tools –> Create Font … erstellt werden.

Image


PFont font; //erstellt ein Pfont-Objekt mit dem Namen font
void setup() {
size(300,300);
font = loadFont("ArialMT-48.vlw"); //weist dem Objekt font die Grafiken für die einzelnen Buchstaben zu
textFont(font); //estellt ein Objekt textFont mit dem Parameter font
}

void draw() {
background(200);
text(key,90,110); //gibt text auf dem Schirm aus
}

Im Android Mode muss man die virtuelle Tastatur aktivieren!

Die key-Variable kann benutzt werden, um zu prüfen, welche Taste gedrückt wurde. Hier ist es auch wichtig bei der Abfragen mit den logischen Operatoren && (logisches UND) und || (logisches ODER) zu arbeiten.

TIPP: weil alle Zeichen über den ASCII-Code festgelegt werden, kann man mit der Variable key auch rechnen. Sie gibt, dann den ASCII-Wert der letzten Tastatureingabe zurück.

Eine weitere Variable keyCode speichert ALT, CONTROL, SHIFT, UP, DOWN, LEFT und RIGHT. Bevor man diese codierten Tasten abfragt, sollte man mit key == CODED (hoffentlich true) abfragen, ob eine Taste codiert ist.

Events


Processing 2.0

Events sind spezielle Funktionen, die den Programmfluss unterbrechen können. Dabei unterbrechen sie den Programmfluss immer nach einem Durchlauf von draw(). Sie stören also den Ablauf innerhalb der draw()-Funktion nicht. Die Eventparameter werden bis zum Ende eines draw()-Durchlaufs gespeichert. Danach läuft die Event-Funktion genau einmal durch.

Maus-Events:
mousePressed()    //läuft einmal ab, wenn eine Maustaste gedrückt wird.
mouseReleased()  //läuft einmal ab, wenn eine Maustaste losgelassen wird.
mouseMoved() //läuft einmal ab, wenn der Mauszeiger bewegt wird.
mouseDragged() //läuft einmal ab, wenn der Mauszeiger bewegt wird, während ein Taste gedrückt worden ist.

Keyboard-Events:
keyPressed()     //läuft einmal ab, wenn eine Taste gedrückt wird.
keyReleased()   //läuft einmal ab, wenn eine Taste losgelassen wird.

Beispiel: starte Applet Zeichenprogramm
Image

void setup()
{
 size(300, 300);
 fill(255,50);
 noStroke();
 noLoop();//dadurch wird draw nicht automatisch wiederholt
 background(0);
}

void draw()
{
 ellipse(mouseX, mouseY,5,5);
}

void mouseDragged()
{
 redraw(); //lässt den Code in draw ein mal ablaufen
}

Dieses Beispiel lässt draw() immer dann einmal durchlaufen, wenn die Maustaste gedrückt ist und die Maus bewegt wird. Dies wird durch ein Event getriggert.

Aufgabe: Schreibe das Programm Pong so um, dass die Steuerung über Events erfolgt.



Facebook Anwendung Teil1


Ziel dieses Projektes ist es, eine Facebook-Anwendung zu erstellen, bei der man mit Processing auf Informationen von Facebook zugreifen kann. Konkret möchte ich den aktuell eingeloggten Facebook identifizieren (als JavaScript umgesetzt) und seine Facebook UserID in mein Processing Applett übertragen. Dann werden Userdaten und das UserImage heruntergeladen und auf den Bildschirm gezeichnet.Damit ist Teil 1 erledigt.

Wie bekomme ich einen App-Account?

Einen App-Account kann jeder Facebook User erstellen.Dafür meldet man sich bei Facebook an und aktiviert die Anwendung „Developer“. Dort kann man dann rechts oben eine „Neue Anwendung erstellen“.

http://www.facebook.com/developers/

Neuerdings muss dann das eigene Konto verfiziert werden und würde euch dringend raten das mit Handy und nicht mit der Kreditkartennummer zu machen (auch Handy ist unsicher, meiner Ansicht nach noch das kleinere Übel).  Beim registrieren der App sind dann folgende Einstellungen vorzunehmen:

  • Anwendungs-ID (wird zugewiesen, ist dann in die von mir erstellte Seitenvorlage einzufügen)
  • API-Schlüssel (wird dann im Processing Applet benötigt)Image
  • Anwendungs-Geheimcode (für Processing Applet)

    Image

  • Seitenadresse ist auch bei Canvas-URL einzutragen (reale Internetadresse deiner Seite (Bsp: http://xxxx.bplaced.net/xxxxxx)
  • Leinwandadresse oder Canvas-Seite (die Adresse deiner App in Facebook – die Seite liegt nicht wirklich dort!!! Bsp: http://apps.facebook.com/xxxxxxxxx/Image
  • Sandkastenmodus aktivieren (nur du hast Zugriff auf deine App)

    Image

Hier gibt es allgemeine Info zur App-Registrierung.

Prinzipiell funktioniert das so, dass man auf irgendeiner URL seine Seite veröffentlicht. Diese wird dann in einem sog. IFrame (HTML-Fenster) unter der Adresse  der Facebook app angezeigt.

Erstellen eines geeigneten HTML – Javaskript-Unterbaus

Hilfreich ist dieses Tutorial . Hier findet man ein Seitengerüst mit dem man über HTML/JavaSkript Abfragen auf Facebook durchführen kann.Dafür wird die aktuelle von Facebook angebotene Programmierschnittstelle, die Graph-API verwendet. JavaSkript kann nun zum Beispiel einen Login/Logout Button rendern und den aktuellen Anmeldestatus abfragen.

Konkret brauchen wir dieses Gerüst um erst einmal an die UserID des Besuchers der Seite zu kommen. Ich habe diesen Unterbau ein wenig modifiziert. Damit die Seite funktioniert muss man noch die eigene Facebook App-ID einsetzen.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:fb="http://www.facebook.com/2008/fbml">
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
 <title>MyFriends</title>
 </head>
 <body>
 <div id="fb-root"></div>
 <script type="text/javascript">
 window.fbAsyncInit = function() {
 FB.init({appId: 'YourAppID', status: true, cookie: true, xfbml: true});

 /* All the events registered */
 FB.Event.subscribe('auth.login', function(response) {
 // do something with response
 login();
 });
 FB.Event.subscribe('auth.logout', function(response) {
 // do something with response
 logout();
 });

 FB.getLoginStatus(function(response) {
 if (response.session) {
 // logged in and connected user, someone you know
 login();
 }
 });
 };
 (function() {
 var e = document.createElement('script');
 e.type = 'text/javascript';
 e.src = document.location.protocol +
 '//connect.facebook.net/en_US/all.js';
 e.async = true;
 document.getElementById('fb-root').appendChild(e);
 }());

 function login(){
 FB.api('/me', function(response) {
 document.getElementById('login').style.display = "block";
 document.getElementById('login').innerHTML = response.name + "
 " +response.id + " succsessfully logged in!";
 document.Hallo.setString(response.id)
 });
 }
 function logout(){
 document.getElementById('login').style.display = "none";
 }

 //stream publish method
 function streamPublish(name, description, hrefTitle, hrefLink, userPrompt){
 FB.ui(
 {
 method: 'stream.publish',
 message: '',
 attachment: {
 name: name,
 caption: '',
 description: (description),
 href: hrefLink
 },
 action_links: [
 { text: hrefTitle, href: hrefLink }
 ],
 user_prompt_message: userPrompt
 },
 function(response) {

 });

 }
 function showStream(){
 FB.api('/me', function(response) {
 //console.log(response.id);
 streamPublish(response.name, 'Thinkdiff.net contains geeky stuff',
'hrefTitle', 'http://thinkdiff.net', "Share                 thinkdiff.net");
 });
 }

 function share(){
 var share = {
 method: 'stream.share',
 u: 'http://thinkdiff.net/'
 };

 FB.ui(share, function(response) { console.log(response); });
 }

 function graphStreamPublish(){
 var body = 'Reading New Graph api & Javascript Base FBConnect Tutorial';
 FB.api('/me/feed', 'post', { message: body }, function(response) {
 if (!response || response.error) {
 alert('Error occured');
 } else {
 alert('Post ID: ' + response.id);
 }
 });
 }

 function fqlQuery(){
 FB.api('/me', function(response) {
 var query = FB.Data.query('select name, hometown_location,
sex, pic_square from user where uid={0}', response.id);
 query.wait(function(rows) {

 document.getElementById('name').innerHTML =
 'Your name: ' + rows[0].name + "<br />" +
 '<img src="' + rows[0].pic_square + '" alt="" />' + "<br />";
 });
 });
 }

 function setStatus(){
 status1 = document.getElementById('status').value;
 FB.api(
 {
 method: 'status.set',
 status: status1
 },
 function(response) {
 if (response == 0){
 alert('Your facebook status not updated. Give Status Update Permission.');
 }
 else{
 alert('Your facebook status updated');
 }
 }
 );
 }
 </script>
 <h3>MyFriends</h3>
 <p><fb:login-button autologoutlink="true" perms="email,user_birthday,
status_update,publish_stream"></fb:login-button></p>
 <div id="login" style ="display:none"></div>
 <div id="name"></div>
 <script src="http://connect.facebook.net/en_US/all.js#xfbml=1"></script>
<fb:like href="http://apps.facebook.com/YourAppAddress"       
 show_faces="true" width="450"></fb:like>
###hier kommt das Processing-Applet hin###
 </body>
</html>
 function login(){
 FB.api('/me', function(response) {
 document.getElementById('login').style.display = "block";
 document.getElementById('login').innerHTML = response.name + "
 " +response.id + " succsessfully logged in!";
 document.Hallo.setString(response.id)
 });
 }

Die UserID an das Applet übertragen

Damit die UserID nun von JavaSkript (läuft schon in der HTML-Seite) in das Processing Applet übertragen werden kann, habe ich in dem Beispiel oben die Funktion login um diese Zeile ergänzt:

 function login(){
 FB.api('/me', function(response) {
 document.getElementById('login').style.display = "block";
 document.getElementById('login').innerHTML = response.name + "
 " +response.id + " succsessfully logged in!";
 document.Hallo.setString(response.id)
 });
 }

Sie bewirkt, dass in einem Applet mit dem Namen Hallo die Funktion setString aufgerufen, und ein String (response.id –> dieser enthält die Facebook-UserID des eingeloggten Users) an das Applet übergeben wird.Damit das funktioniert, braucht man die Library java.applet.Applet. Sie wird mit folgender Anweisung importiert: import java.applet.Applet;

Die Funktion sieht im unserem Fall so aus:

public void setString(String aString)
{
 fbUserIDs = aString;
 MyIDisloaded =true;
}

In der markierten Zeile wird die von JavaSkript übergebene String Variable aString an die globale Variable fbUserIDs übergeben.

Das funktioniert aber nur, wenn man dem exportierten Processing Applet noch händisch einen Namen, wie in diesem Fall, Hallo, zuweist.

var attributes = {
 code: 'facebook_myid_1_0.class',
name:'Hallo',
 archive: 'facebook_myid_1_0.jar,jpen-2.jar,generativedesign.jar,core.jar',
 width: 1000,
 height: 600,
 image: 'loading.gif'
 };

Damit haben wir die UserID in dem Applet zur Verfügung.

Der Rest der Daten

Um an die Daten zu kommen, die wir sonst noch für unser Projekt benötigen, benutzen wir nun eine andere, etwas ältere Programmierschnittstelle von Facebook, nämlich die Old REST API.  Auf http://wiki.processing.org/w/Facebook,_REST_API findet man Beispielcode, um Daten über die Facebook API abzurufen. Man schickt eine rel. komplexe Anfrage und bekommt als Antwort ein XML-File, aus dem man dann die Daten extrahieren und in die einzelnen Strings umwandeln muss. Ich habe die Anfragen, die Überwachung des asynchronen Ladevorgangs und auch das Verarbeiten der XML-Files in eine Klasse mit dem Namen Facebook gepackt. Die Methode für die Anfragen sieht so aus:

void getmyXML () {
 int currentUser = 0;
 String xmlRequest = this.CallMethod( new String[] {
 "method=facebook.Users.getInfo",  
 "uids=" + UserIDs,
 "fields=first_name,last_name,hometown_location,pic", // see link above for more options
 "format=XML"
 }
 );
 myXML = GenerativeDesign.loadXMLAsync(thisPApplet, xmlRequest);
 dataloaded = false;
 }

Hier wird in Zeile 3 die Methode CallMethod  aufgerufen, Sie gibt dann die Anfrage mit div. Signaturen und verschlüsselten Elementen als String zurück.

String CallMethod ( String[] args )
 {
 String[] params = new String[args.length + 3];
 System.arraycopy( args, 0, params, 0, args.length );
 params[params.length-3] = "api_key=" + ApiKey;
 params[params.length-2] = "call_id=" + System.currentTimeMillis();
 params[params.length-1] = "v=1.0";

 String sig = this.GenerateSIG ( params );
 String paramString = join( params, "&" ) + "&sig=" + sig;
 String response = RestServer + RestNode + "?" + paramString;
 return response;
 }

 /**
 *  Generate a call signature, see:
 *  http://wiki.developers.facebook.com/index.php/How_Facebook_Authenticates_Your_Application
 *  http://wiki.developers.facebook.com/index.php/Authorization_and_Authentication_for_Desktop_Applications
 */
 String GenerateSIG ( String[] args )
 {
 java.util.Arrays.sort( args );
 String argString = join( args, "" );
 argString += ApiSecret;

 return this.md5Encode( argString );
 }

 /**
 * MD5 encode a String using Processing API ( hex() )
 */
 String md5Encode ( String data )
 {
 java.security.MessageDigest digest = null;
 try {
 digest = java.security.MessageDigest.getInstance("MD5");
 }
 catch ( java.security.NoSuchAlgorithmException nsae ) {
 nsae.printStackTrace();
 }
 digest.update( data.getBytes() );
 byte[] hash = digest.digest();

 StringBuilder hexed = new StringBuilder();

 for ( int i = 0; i < hash.length; i++ )
 {
 hexed.append( hex( hash[i], 2 ) );
 }
 return hexed.toString().toLowerCase();
 }

Dann wird in getXML mit der Anfrage als Parameter die Methode loadXMLAsync aus der generative-design-library aufgerufen (http://www.generative-gestaltung.de/). Dieses asynchrone Laden der Daten ist wichtig, da sonst ja jegliche Animation, die in draw() läuft, unterbrochen würde, bis die Daten geladen sind. So werden die Datensätze im Hintergrund geladen, während das Programm weiterläuft. Um die fertig geladenen Daten dann im laufenden Programm verwenden zu können, muss man nun immer wieder prüfen, ob sie schon fertig geladen sind. Das macht bei mir die Methode loaderloop(), bzw. imageloaderloop(). Diese werden immer wieder in draw() aufgerufen.

boolean loaderloop () {
 if (myXML != null) {
 if (myXML.hasChildren() == false) {
 println("not loaded yet");
 }
 else {
 dataloaded=true;
 return dataloaded;
 }
 }
 return dataloaded;
 }

 boolean imgloaderloop () {
 if (imgisloading) {
 if (myImage.width == 0) {
 // image is not yet loaded
 println("not loaded yet");
 }
 else if (myImage.width == -1) {
 // this means an error occurred during image loading
 }
 else {
 // image is ready to go, draw it
 imgloaded=true;
 imgisloading =false;
 return imgloaded;
 }
 }
 return imgloaded;
 }

Sind die XML-Daten geladen, wird (auch aus draw()) die Methode getMyData() aufgerufen und das XML File wird in die einzelnen Strings umgewandelt. Von hier aus wird dann auch, nachdem man die Adresse für das Profilbild ausgelesen hat der asysnchrone ImageLoader aufgerufen. Der dann das Profilbild analog zu den Daten vorher aus dem Web lädt.

String getmyData () {
 usersXml = myXML.getChildren();
 String myinfo = usersXml[currentUser].getChild("first_name").getContent();
 myinfo += " "+usersXml[currentUser].getChild("last_name").getContent();
 String mypicurl= usersXml[currentUser].getChild("pic").getContent();
 myImage = GenerativeDesign.loadImageAsync(thisPApplet, mypicurl);
 imgisloading=true;
 myXML = null;
 dataloaded = false;
 return   myinfo;
 }

Zu guter Letzt gibt es noch das Hauptprogramm, aus dem dann diese Objekte und Methoden aufgerufen werden. Die Erklärungen dazu sind als Kommentar im Code enthalten.

//import der benötigten Programmbibliotheken
import generativedesign.*;
import java.awt.Graphics;
import java.applet.Applet;

// application api key and secret
String fbApiKey = "Dein Appkey";
String fbApiSecret = "Dien AppSecret";

// Initialisieren der Variablen und Objekte
String fbUserIDs = null;
facebook mydata;
PFont font;
PApplet thisPApplet = this;
boolean MyIDisloaded = false;
boolean MyObjektiscreated =false;
String myfull_name=null;
PImage my_image=null;

void setup ()
{
 size( 1000, 600 );
 fill( 250);
 stroke(29,64,136);
 strokeWeight(3);
 smooth();
 textAlign( CENTER );
 font = createFont("Impact", 12);
}

void draw ()
{
 background(50);
 //hier die Abfage, ob die UserId schon im Applet angekommen ist
 if (MyIDisloaded && !MyObjektiscreated) {
 mydata = new facebook(fbApiKey, fbApiSecret, fbUserIDs);
 mydata.getmyXML();
 MyObjektiscreated =true;
 }
 //wenn nicht, wird ... is loading angezeigt
 if (!MyIDisloaded && !MyObjektiscreated) {
 text("...is loading", 20,20);
 }
 // andernfalls wird geprüft, ob die XML-Daten schon vorhanden sind
 else {

 if (mydata.loaderloop()) {
 //wenn ja, werden diese als Strings abgerufen
 myfull_name=mydata.getmyData();   
 }
 // analog wird auch beim Profilbild geprüft, ob es schon geladen ist
 if (mydata.imgloaderloop()) {
 // wenn ja, wird es in my_image gespeichert
 my_image=mydata.getmyImage();
 }
 }
 //wenn das Bild dann da ist, wird am Bildschirm ausgegeben.
 if(my_image!=null) {
 fill(98,122,173);
Zip-File ellipse(width/2,height/2+10,200,200);
 fill(255);
 text(myfull_name,width/2,height/2+my_image.height/2+20);
 image(my_image,width/2-my_image.width/2,height/2-my_image.height/2);
 }
}

public void setString(String aString)
{
 fbUserIDs = aString;
 MyIDisloaded =true;
}

Hier das Ganze noch mal als Zip-File.

Funkt aber nur, wenn vorher eine Facebook App registriert und die entsprechenden Schlüssel in der Datei eingetragen wurden.

Projekt Ping Pong


Processing 2.0

Mit den bis jetzt bearbeiteten Dingen kann man schon eine Menge machen. Wir programmieren unser erstes Spiel. Und zwar ein Ping Pong Spiel. Erläuterungen siehe unten im Quellcode.

Beispiel: starte Applet

Hinweis: Funktioniert nicht im Android Modus!

Hinweis: Damit die Fehler im JavaScript Modus angezeigt werden, muss die Web-Konsole in Firefox aktiviert werden. (Extras –> Web Entwickler!)

Image

/** Copyright 2012 Thomas Koberger
*/

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

int ballsize = 20;       // Balldurchmesser
float xpos, ypos;    // Position des Balles
float xspeed = random(5,6);  // Geschw. des Balls in x-Richtung
float yspeed = random(0,4);  // Geschw. des Balls in y-Richtung
int fehler=0;
int playerpos;

void setup()
{
size(640, 400);
noStroke();
playerpos =height/2;
xpos = 45;
ypos = playerpos;
}

void draw()
{
background(100);
// Bewegung des Balles
xspeed *= 1.001;
xpos += xspeed;
ypos += yspeed;

//damit der Ball zurückprallt (rechts, oben, unten)
if (xpos > width-ballsize/2){
xspeed *= -1;
}
if (ypos > height-ballsize/2 || ypos < 0+ballsize/2) {
yspeed *= -1;
}

// Begrenzung links und Fehler
if ( xpos <= 30+ballsize/2) {
if (ypos > playerpos-50 && ypos < playerpos+50){
xspeed *= -1;
yspeed += (ypos-playerpos)/5;
}
}
//Fehler
if (xpos <= 0){
fehler=fehler+1;
if (fehler <= 5){
xpos = 50;
ypos = playerpos;
xspeed *= -1;
yspeed = 0;
}
}


// Zeichnen des Balls und des Rechtecks
ellipse(xpos, ypos, ballsize, ballsize);
rect (10, playerpos-50,20,100);
//Ausgabe Fehlerzahl
println("fehler: "+ fehler );

// Bewegung Rechteck
if (keyPressed) {
if (key == 'w') {
playerpos = playerpos - 8;
}
if (key == 'y') {
playerpos = playerpos + 8;
}
}

}
}

Aufgabe1: Verändere das Programm so, dass ein Fehler (int fehler) oder Bälle (5-int fehler) und Punkte (mit der Systemvariablen frameCount) am Display ausgegeben werden.

Aufgabe2: Verbessere die Physik des Spiels.

Aufgabe3: Verändere das Programm so, dass 2 Spieler gegeneinander spielen können.

Farbe


Processing 2.0

Hier kommt bei Processing, wie bei vielen anderen Programmen zunächst das sog. RGB-Modell zum Einsatz. D.h. jede Farbe wird aus einem Rot-, Grün- und Blauwert zwischen 0 und 255 definiert. Konkret wird dann aus einer Mischung aller Farben mit der maximalen Intensität (255,255,255) –> Weiß und (0,0,0) ergibt Schwarz.

ImageWenn man alle drei Farben zu gleichen Teilen mischt, ist das Ergebnis immer ein Grauton. Seine Intensität ergibt sich aus der Intensitäten der Einzelfarben.

Mit den Funktionen

background(r,g,b);
fill(r,g,b);
fill(r,g,b,alpha);
stroke(r,g,b); und
stroke(r,g,b,alpha);

kann man die Eigenschaften der Elemente am Schirm definieren. Alpha definiert die Deckkraft eines Objekts (0…durchsichtig bis 255…volle Deckung).

Processing bringt im Menü unter Tools auch den Color Selector mit dem man Farben auswählen und dann deren RGB- und HSB- Werte ablesen kann.

Beispiel: starte Applet

Bewege deine Maus über die Animation und bestimme damit die Deckkraft der Farbkreise.

Image

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

void draw()
{
background(200);
fill(255,0,0,mouseX*2);
ellipse(150,100,150,150);
fill(0,255,0,mouseX*2);
ellipse(100,170,150,150);
fill(0,0,255,mouseX*2);
ellipse(180,170,150,150);
}

Beispiel: starte Applet

Image

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

void draw() {
background(262, 202, 49);
for (int i=0; i<300; i++) {
stroke(0, 0, 255, i-(100));
line(i, 0, i, 300);
}
}

Aufgabe: Verändere den Code des letzten Beispiels so, dass sich der Übergang je nach Mausposition nach links und recht verschieben lässt.

Um den Umgang mit Farben noch etwas einfacher zu gestalten, kann man in Processing auf einen eigenen Datentyp, nämlich color() zugreifen. Damit kann man Farben, die man öfters verwendet Namen zuordnen. Die Farbdefinition funktioniert, wie sonst auch, als Grauwert oder RGB, mit oder ohne alpha-Wert.

Beispiel: starte Applet

Die Farbe des Rechtecks ändert sich je nach Mausposition von einer vorher definierten Farbe grün zu blau.

Image

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

void draw() {
background(255,188,3);
color blue=color(3,206,255);
color green = color(195,255,3);
if (mouseX<150){
fill(blue);
} else {
fill(green);
}
rect(50,50,100,100);
}

Neben dem Standard-Farbmodell RGB kann man in Processing auch das HSB-Modell anwenden. Hier wird eine Farbe mit der Angabe des Farbtons (hue), der Farbintensität (straturation) und der Helligkeit (brightness) angegeben. Mit der Funktion colorMode(mode) kann man das Fabmodell wählen. Mode kann somit für RGB oder HSB stehen.

Beispiel: starte Applet

Image

int a=1;
int c=1;

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

void draw(){
a++;
for(int i=0; i<width; i++){
c=(a+i)%255;
stroke(c,255,255);
line(i,0,i,300);
}
}

Aufgabe: Gestalte das Spiel Ping Pong farbig.

Fotos


Processing 2.0

Auch für Bilder bringt Processing mit PImage einen eigenen Datentyp mit. Um ein Bild auszugeben geht man also folgendermaßen vor: Man deklariert eine Variable (genauer ein Objekt) vom Typ PImage. Dann weist man ihr mit der Funktion loadImage(„dateiname“) einen Wert zu.  Um sie dann anzuzeigen verwendet man die Funktion image(var,  x, y, breite,  höhe). Processing kann Bilder der Formate jpg, gif und png verarbeiten.

Beispiel: starte Applet

Image

PImage foto;//deklariert die Variable foto;

void setup(){
 size(300, 300);
 foto = loadImage("blume.JPG");
//weist der Variablen foto die datei blume.jpg zu
}

void draw(){
 image(foto, 0, 0, 300,300);
//gibt die Variable foto auf dem Bildschirm aus
}

Um Bilder zu verändern kann man beispielsweise die Funktion tint(); verwenden, die wie fill() und stroke() verwendet werden können.

Aufgabe: Schreibe ein Programm die Blume je nach Mausposition rot und grün einfärbt.

Will man komplexere Veränderungen vornehmen, kann man mit der Methode loadPixels() kann man die einzelnen Bildpunkte als Daten vom Typ Color in ein Array laden.

Siehe Projekt: Bild aus Text

Zufall


Processing 2.0

Auch in Processing gibt es Möglichkeiten Zufallszahlen zu erzeugen. Die einfachste Möglichkeit bietet hier die Funktion random(). Manchmal will man aber Zufallszahlen haben, die jeweils nur leicht voneinander abweichen, sowie in der Natur. Für diese Aufgabe gibt es die noise() – Funktion, die immer Zahlen zwischen 0 und 1 ausgibt. Durch den folgenden Parameter kann man nur die Größe der Abweichung von Zahl zu Zahl beeinflusst werden.

random(hoch, tief)

Beispiel: starte Applet
Von diesem Programm werden an zufälligen Positionen Ellipsen gezeichnet. Auch die Strichstärke ist variabel. Dadurch entstehen große und kleine Formen.

Image

void setup()
{
size(300,300);
stroke(0,40);
}

void draw()
{
int x=int(random(300));
int y=int(random(300));
int z=int(random(10));
strokeWeight(z);
ellipse(x, y, 1,1);
}

randomSeed(Wert)

Mit randomSeed() und einem Integer als Wert kann man, wenn man will (und der Integer-Wert immer der gleiche ist) immer die gleiche Zufallszahlenreihe erzeugen.

Aufgabe: Schreibe ein Programm, das in etwa folgenden Output erzeugt.

Image

noise(x,y,z)

Die noise– Funktion kann mit einem, zwei oder drei- Parametern aufrufen, je nachdem, ob man 1-,2- oder 3-dimensional arbeiten will. Erzeugt werden immer Zahlenwerte zwischen o und 1, die jeweils nur geringe Unterschiede zu den Zahlen davor aufweisen. Will man natürliche Vorgänge simulieren, ist diese Funtktion sehr sinnvoll.

Die Werte von x, y und z müssen, um unterschiedliche Zunoise– Funktionfallszahlen zu generieren bei jedem Durchlauf erhöht werden. Größe der Schritte definiert dann die Variabilität der Zahlenreihe. Für die meisten Anwendungen eignen sich Schritte von 0.005 bis o.1.

Beispiel: starte Applet

Image

float v = 0.0;
float inc = 0.01;
float next= 0.01;

void setup()
{
size(300,300);
noStroke();
fill(50);
}

void draw()
{
translate(width/2, height/2);
float b = noise(v) * 5.0;
float n = next/1.0;
rect (next*cos(n)+b,next*sin(n)+b, 1, 1);
v=v+inc;
next = next+0.01;
}

Beispiel: starte Applet

Image

float v = 0.0;
float inc = 0.01;
float next= 0.8;

void setup()
{
 background(255);
 smooth();
 size(300,300);
 noStroke();
 frameRate(120);
 colorMode(HSB);
}

void draw()
{
 translate(width/2, height/2);
 float b = noise(v) * 10.0;
 float n = next;
 fill(frameCount%180,255,255,50);
 ellipse (next*cos(n)+b,next*sin(n)+b, 5*b, 5*b);
 v=v+inc;
 next = next+0.1;
}

Gauss’sche Verteilung

Denken wir an die Größenverteilung bei Menschen. Ein durchschnittlicher männlicher Österreicher ist 1,78 m groß. Betrachten wir nun eine Menge von Menschen, dann ist klar, dass sehr wenige über 2 m groß, oder kleiner als 1,65  sind. Die meisten sind etwa so groß, wie der Durchschnitt. Das heißt: Je größer die Abweichung vom Mittel, desto weniger Menschen dieser Größe wird es geben. Eine entsprechende Kurve sieht dann so aus:

Image

In Processing kann man solche Zahlenverteilungen mit der Random – Klasse erzeugen. Die Klasse kann folgendermaßen verwendet werden: Objekt erstellen:

Random gauss;

Dann Objekt initialisieren:
gauss = new Random();

Nun kann man mit der folgenden Codezeile die Zahlen erzeugen:

float num = (float) generator.nextGaussian();

 

XMLElement


Viele große Internetportale bieten mittlerweile Programmierschnittstellen, sog. API’s an. Über diese kann man z.B. mit Processing Daten abrufen. Die Daten werden per URL angefordert. Die Antwort kommt meist (nicht immer) per XML-File. XML ist eine Auszeichnungssprache (Extensible Markup Language). Dabei werden die Daten in sog. Tags, ähnlich wie in HTML verpackt.

Um diese Daten verarbeiten zu können, gibt es in Processing Das Objekt XMLElement. In diesem können ganze XML Files gespeichert werden. XMLElement bringt Methoden mit, um einzelne Datensätze auslesen zu können.

Eine Anfrage, wie im Projekt FacebookAnwendung 1 sieht dann z.B. so aus (mit println() ausgelesen):

http://api.facebook.com/restserver.php?api_key=YourAPIKey&call_id=YourAPPID&fields=first_name,last_name,hometown_location,pic&format=XML&method=facebook.Users.getInfo&uids=YourUserID&v=1.0&sig=4c0d5ef0edf23eb16d95ed17e4fab781

Als Antwort schickt Facebook dann die folgende Datei:

<Users_getInfo_response xsi:schemaLocation="http://api.facebook.com/1.0/ 
http://api.facebook.com/1.0/facebook.xsd" list="true">
 <user xmlns="http://api.facebook.com/1.0/">
   <first_name xmlns="http://api.facebook.com/1.0/">YourFirstName</first_name>
   <hometown_location xsi:nil="true"/>
   <last_name xmlns="http://api.facebook.com/1.0/">YourLastName</last_name>
   <pic xmlns="http://api.facebook.com/1.0/">
      Image
   </pic>
   <uid xmlns="http://api.facebook.com/1.0/">YourUserID</uid>
   </user>
</Users_getInfo_response>

Alle diese Infos zwischen Tags, also <first_name>YourFirstName</first_name> nennt man Elemente. Diese Elemente sind hierarchisch gegliedert, d.h. <user> wäre das Parent Element von <first_name>, <last_name>, <pic>, usw. Diese sind dann also Child-Elemente von <user>. xmlns=“http://api.facebook.com/1.0/&#8220; steckt in einem öffnenden Tag und ist eine sog. Attribut.

Angenommen wir haben die obige Antwort in ein XMLElement myXML gespeichert, so können wir mit folgenden Anweisungen die Daten auslesen:

  • myXML.getChild(„user“)
    gibt das Child-Element von <Users>, also <User> zurück

    • <user xmlns="http://api.facebook.com/1.0/">
         <first_name xmlns="http://api.facebook.com/1.0/">YourFirstName</first_name>
         <hometown_location xsi:nil="true"/>
      <last_name xmlns="http://api.facebook.com/1.0/">YourLastName</last_name>
      <pic xmlns="http://api.facebook.com/1.0/">
      Image
      </pic>
      <uid xmlns="http://api.facebook.com/1.0/">YourUserID</uid>
      </user>
  • myXML.getChild(„user/first_name“)
    • <first_name xmlns="http://api.facebook.com/1.0/">YourFirstName</first_name>
  • myXML.getChild(„user“).getContent()
    gibt den Inhalt des Elements <User> zurück, in dem Fall leer, also null

    • null
  • myXML.getChild(„user/first_name“).getContent()
    gibt den Inhalt des Elements <First_Name> zurück

    • YourFirsName
  • myXML.getChildren()
    gibt alle Child-Elemente eines Elements als String-Array zurück z.B.:
  • myXML.getChild(„user“).getChildren()
    • [0]   <first_name xmlns="http://api.facebook.com/1.0/">YourFirstName</first_name>
      [1]   <hometown_location xmlns="http://api.facebook.com/1.0/" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
      [2]   <last_name xmlns="http://api.facebook.com/1.0/">YourLastName</last_name>
      [3]  <pic xmlns="http://api.facebook.com/1.0/"> 
      http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs272.snc3/23196_YourUserID_7319_s.jpg</pic>
    • [4]   <uid xmlns="http://api.facebook.com/1.0/">YourUID</uid>

Den Content der Elemente kann man entweder als String, oder Array abspeichern und dann im Programm weiterverarbeiten.

Darüber hinaus gibt es auch noch die Möglichkeit selbst XMLElemente zu erstellen. Eine komplette Elementreferenz findet man hier http://processing.googlecode.com/svn/trunk/processing/build/javadoc/core/index.html.

Strings


Processing 2.0

Variablen des Datentyps String können Zeichenketten enthalten. Im Gegensatz zu char, int oder float handelt es sich beim String nicht um einen „primitiven“ Datentyp, sondern um ein Objekt (siehe Objekte und Klassen).

Während der Datentyp char ein Zeichen enthalten kann und dieses in Apostrophen ‚ ‚ gesetzt werden muss, enthält ein String in der Regel mehrere Zeichen und diese werden in normale Anführungszeichen “ “ gesetzt.

Beispiel:

String A ="Hallo  Welt!";
println(A); 

Gibt „Hallo Welt“ im Terminalfenster aus.

Der Datentyp String bringt viele Methoden mit. Mit ihnen können die Daten im Objekt manipuliert werden.

Methoden von String:

  • String.length(); //gibt die Anzahl der Zeichen zurück.

Beispiel:

String A ="Hallo  Welt!";
println(A.length()); 

Gibt 12 im Terminalfenster aus. Beachte die runde Klammer nach length(). Sie ist hier unbedingt nötig, da es sich im Unterschied zum Array.length hier um eine Methode handelt.

  • String.startsWith(); //gibt je nach Übereinstimmung true oder false zurück
    String.endsWith(); //gibt je nach Übereinstimmung true oder false zurück

Beispiel:

String A ="Hallo  Welt!";
println(A.startsWith("Hallo"));
println(A.endsWith("Hallo"));
println(A.endsWith("Welt!")); 

Dieses Beispiel gibt true, false, true zurück, da die jeweiligen Zeichenketten mit dem Beginn oder dem Ende des Strings A übereinstimmen, oder auch nicht.

  • String.charAt(); //gibt das Zeichen an der in Klammer angegebenen Position zurück (beginnt nat. mit 0)

Beispiel:

String A ="Hallo Welt!";
println(A.charAt(6)); 

Gibt ein ‚W‘ im Terminalfenster aus.

String.toLowerCase(); //wandelt den String in Kleinbuchstaben um
String.toUpperCase(); //wandelt den String in Großbuchstaben um

Beispiel:

String A ="Hallo Welt!";
println(A.toLowerCase());
println(A.toUpperCase()); 

Gibt hallo welt und HALLO WELT im Terminalfenster aus.

  • String.substring(); // gibt den String ab der in Klammer angegebenen Stelle, oder zwischen diesen Stellen zurück

Beispiel:

String A ="Hallo Welt!";
println(A.substring(4));
println(A.substring(3,8)); 

Gibt „o Welt!“ und „lo We“ zurück!

  • String.equals(); // vergleicht 2 Strings und gibt true oder false zurück!

Da Strings keine primitiven Datentypen sind, können sie nicht mit == verglichen werden.

  • String.toCharArray(); // legt die Zeichen des Strings in einem Array ab.

Beispiel:

String A ="Hallo Welt!";
char [] c = A.toCharArray();
println(c[0]);
println(c[6]); 

Gibt H und W im Terminalfenster aus.

Funktionen


Funktionen dienen dazu, oft wiederkehrende Codebereiche (Berechnungen, Formen usw.) nur einmal eingeben zu müssen, und sie dann immer wieder aufrufen zu können.

In der Regel geben Funktionen etwas zurück (das Resultat der Berechnung). Deshalb werden die Rückgabewerte von Funktionen auch einem Datentyp zugeordnet (int, float usw.)

Beispiel:

void setup() {
 float ergebnis = multipliziere(3.5, 2.0); // hier wird die funktion aufgerufen und der return-wert in der variable ergebnis gespeichert
 println (ergebnis);
}

float multipliziere (float zahl1, float zahl2) {
 return (zahl1*zahl2);  // hier wird der Rückgabewert definiert
}

In diesem Beispiel wird das Ergebnis der Funktion multipliziere in der Variable ergebnis gespeichert und dann per println am Bildschirm ausgegeben.

Eine Funktionen kann man von Variablen i. a. dadurch unterscheiden, dass bei ihrem Aufruf Parameter übertragen werden, die dann in einer runden Klammer angegeben werden.

frameRate….Variable

multipliziere (float zahl1, float zahl2)…Funktion

Wird eine Funktion, die beispielsweise eine komplexe Figur zeichnet, ohne Parameter aufgerufen, so bleiben die runden Klammern ohne Inhalt.

Die Funktionen setup() und draw()

Auch setup() und draw() sind Funktionen, allerdings welche die keine Werte zurückgeben. Solchen Funktionen wird eine void -Anweisung vorne angestellt.

void setup() ; void draw()

Außerdem haben diese beiden Funktionen in Processing eine spezielle Bedeutung, da die Funktion setup() immer als erste nach Programmstart genau ein mal ausgeführt wird, und dann (falls nicht anders definiert –> noLoop) die Funktion draw() immer wieder ausgeführt wird, bis das Programm selbst, oder der Benutzer das Programm stoppt.

Beispiel: Augen starte Applet

Image

int mx,my; //definiert 2 globale variablen

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

void draw() {
background(200);
gesicht(); //zeichnet den kopf und den schnabel
augen(110,height/2); //zeichnet das linke auge
augen(190,height/2); //zeichnet das rechte auge
}

void gesicht() {  //funktion ohne rückgabewert
fill(200);
ellipse  (150,150,160,160);
fill(200);
bezier(110,190,110,260,120,260,190,190);
}

void augen(int x,int y) { //funktion ohne rückgabewert
fill(255);
ellipse(x,y,60,60);
mx=mouseX-x;
my=mouseY-y;
fill(50);
ellipse(x+mx/10,y+my/10,20,20);
}


Winkel und Wellen


Processing 2.0

Für die Erstellung von Grafiken sind Sinus-Kurven und Spiralen von essentieller Bedeutung. Mit Processing sind diese Formen sehr einfach umzustzen.

Zuerst aber etwas Mathematik. Winkel können in Grad ° und in Rad angegeben werden, wobei eine volle Umdrehung 360° oder 2 π (PI) Rad entspricht. π ist also 3,14. In Processing gibt es dafür die Konstanten PI, QUARTER_PI, HALF_PI und TWO_PI.

Zum Umrechnen dienen die Funktionen radians() – rechnet Grad in Rad, oder degrees() – rechnet Rad in Grad um.

sin und cos

Immer wenn in Processing ein Winkel anzugeben ist, wird dieser vom Programm in Rad erwartet. Wenn man lieber in Grad arbeitet, muss man ihn eben umrechnen (mit radians()).

Beispiel: Sinus starte Applet

Image

size (300,300);
noStroke();

float winkel = 0.0;
for (int x=0; x<= width; x+=1) { //die Schleife ist notwendig, um alle x-Werte zu zeichnen.
fill(0);
float y= 150 + (-sin(radians(winkel))*50.0);//berechnet die y-Werte für eine Sin-Kurve
rect(x,y,1,1);
fill(160);
y= 150 + (-cos(radians(winkel))*20.0);//berechnet die y-Werte für eine Cos-Kurve
rect(x,y,1,1);
winkel += 1; //definiert die Skalierung der Wellen
}

Mit der Sinus- und Cosinus-Funktion kann man auch Kreise und Spiralen aus Objekten generieren.

Beispiel: Kreis starte Applet

Image

noStroke();

size(300,300);
int radius =120;
for (int grad =0; grad<360; grad +=12) {
float winkel = radians(grad);
float x = 150 + (cos(winkel)*radius);
float y =150 + (sin(winkel)*radius);
ellipse(x,y,15,15);
}

Beispiel Spirale: starte Applet

Image

noStroke();

size(300,300);
int radius =10;
for (int grad =0; grad<3600; grad +=12) {
float winkel = radians(grad);
float x = 150 + (cos(winkel)*radius);
float y =150 + (sin(winkel)*radius);
ellipse(x,y,15,15);
radius += 2;
}

Hier wird einfach für jeden Winkel der Radius erhöht. Das bewirkt, dass aus dem Kreis eine Spirale wird.

Aufgabe: Ändere das Programm so um, dass es eine schöne Spirale zeichnet.

Beispiel Vieleck: starte Applet

Image

int ecken = 5; //Anzahl der Ecken!
int winkel;// wird später aus der Anzahl der Ecken berechnet
float x1, y1, x2, y2;

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

void draw() {
//die 2 folgenden Anweisungen lassen die Objekte nach kurzer Zeit verblassen
fill(255, 20);
rect(0, 0, width, height);
//Aufruf der Funktion mit den Parametern:
//Eckenzahl, MauspositionX, MauspositionY, RadiusX, RadiusY
vieleck(ecken, mouseX, mouseY, 80, 80);
}
// Funktion zeichnet Vielecke
void  vieleck (int seiten, int x, int y, int radiusX, int radiusY) {

// winkel entspricht dem Abstander der Ecken in Grad
// z.B.: 3 Ecken:120°; 4 Ecken:90° usw.
winkel = (int) 360/seiten;
//die Schleife wird der Anzahl der Ecken entsprechend aufgerufen
//z.B.: 3 mal beim 3-Eck oder 4 mal beim 4_eck
for (int grade=0; grade<360; grade+=winkel) {
// erster Punkt
x1 =sin(radians(grade))*radiusX+(x);
y1 =cos(radians(grade))*radiusY+(y);

// zweiter Punkt
x2 =sin(radians(grade+winkel))*radiusX+(x);
y2 =cos(radians(grade+winkel))*radiusY+(y);

//zeichnet Linie zwischen Punkt 1 und Punkt 2
line(x1, y1, x2, y2);
}
}

In diesem Beispiel wird eine Funktion verwendet, um ein Vieleck mit beliebig vielen Punkten um die Mausposition am Bildschirm zu zeichnen. Dies wird erreicht, indem man den vollen 360°-Winkel durch die Anzahl der Eckpunkte des Vieleckes teilt.

z.B.: 3 Eckpunkte: 360/3 = 120°

Danach beginnen wir in einer Schleife bei Null° und addieren jeweils den Winkel, der bei der vorherigen Division herausgekommen ist. Bei 360° angekommen brechen wir die Schleife ab.

bei 3 Eckpunkten gibt die Schleife nach der Reihe die Winkel 0,120,240 aus.

Wir berechnen dann aus dem Sinus und dem Cosinus der 3 Winkel die Koordinaten der 3 Punkte des Dreiecks.

Der erste Punkt kommt unter der aktuellen Mausposition zu liegen, da wir die X-Achse mit dem Sinus und die Y-Achse mit dem Cosinus berechnen. Bei Null° bleibt also der X-Wert gleich, da der Sin von 0 ebenfalls 0 ist. Der Cos von 0 ist aber 1 und somit verschiebt sich der erste Punkt in pos. Y-Richtung nach unten. Dann werden die weiteren 2 Punkte nach dem gleichen Schema berechnet.

Um dann noch aus den einzelnen Punkten eine Form zu bekommen, brauchen wir in jedem Schleifendurchlauf noch einen weiteren Punkt, der entweder dem aktuellen um den Winkel voraus- oder nacheilt.

Transformationen


Processing 2.0

Mit der Funktion translate(x ,y ); kann man das ganze Koordinatensystem verschieben. Werden danach Formen gezeichnet, addieren sich die x- und y-Koordinaten der Form und der translate()-Anweisung. Auch mehrere translate()– Anweidungen addieren sich.

Image

Image

Außerdem kann mit der Funktion pushMatrix() die Position des Koordinatensystems gespeichert werden. Mit der Umkehrfunktion popMatrix() wird das mit pushMatrix() gespeicherte Koordinatensystem wieder hergestellt. Die beiden Funktionen können nicht ohne die jeweils andere angewendet werden.

pushMatrix() und popMatrix() können bis zu 32 mal verschachtelt angewendet werden.

Beispiel: starte Applet

Image

noStroke();

size(300,300);
for (int i=0;i<300;i+=5) {
pushMatrix();
translate(i,i);
rect(0,0,3,3);
}

for (int i=0;i<300;i+=5) {
popMatrix();
fill(128,200);
rect(20,20,3,3);
}

Dieses Beispiel zeichnet in der ersten Schleife ein Rechteck immer an der gleichen Position im Koordinatensystem. Das Koordinatensystem wird aber bei jedem Schleifendurchlauf weiter verschoben und somit auch das gezeichnete Rechteck. In der zweiten der beiden Schleifen wird dann die Verschiebung des Koordinatensystems Schritt für Schritt zurückgenommen und das Rechteck immer um 20 Punkte verschoben gezeichnet.

Die Funktion scale()

Wie mit translate() die Position, kann man nun mit scale() die Größe eines Objekts ändern. Wert steht für einen Multiplikator, schreibe also 1.2 für 120% oder 0.5 für 50%.

scale(wert);
scale(wertx,werty);

Aufgabe: Verändere das obige Beispiel in der Art, dass es die Rechtecke in der ersten Schliefe vergößert und in der zweiten wieder verkleinert.

Die Funktion rotate()

Ähnlich wie bei sclale() und tranlate() kann man mit rotate() das Koordinatensystem verändern. In diesem Fall kann man es drehen.Der Winkel muss in Rad angegeben werden.

rotate(winkel);

Aufgabe: Drehe das Rechteck im obigen Beispiel in jedem Schleifendurchlauf.

Beispiel: starte Applet

Image

void setup() {
size(300,300);
background(0);
stroke(255,20);
frameRate(5);
}

void draw() {
translate(150,150);
strokeWeight(frameCount/4);
rotate(radians(frameCount*10));
line(0,0,100,0);
}

Viele Arten ein Programm zu erstellen


Viel Wege führen nach Rom. Das gilt auch für die Programmierung. In den Basics haben wir die Grundlagen der Programmierung kennengelernt und unser Augenmerk auf einzelne Befehle oder Programmstrukturen gerichtet. Diese Programme kann man relativ leicht von Grund auf neu schreiben. Je größer und komplexer ein Programm, desto höher ist die Wahrscheinlichkeit, dass zumindest Teile des Codes schon in anderen Programmen vorgekommen sind.

Es ist nichts dagegen einzuwenden, andere Programme, sofern sie keinem Copyright unterliegen, das dies untersagt, als Vorlagen für seine eigenen Projekte zu nutzen. Die einfachste Möglichkeit ein Programm zu verändern liegt darin, die Werte der Variablen zu verändern, wobei die Variationsmöglichkeiten hier natürlich sehr gering sind.

Die Collage-Technik besteht darin, Code-Schnippel aus verschieden Programmen in sein eigenes Projekt zu kopieren. Das ist ein sehr weit verbreiteter Weg, um zu fertigen Projekten zu kommen. Dabei sollte man aber darauf achten, nicht zu viel neuen Code auf einmal einzubinden, da mit der Anzahl der Fehler auch der Aufwand steigt, der nötig ist sie zu beheben.

Processing arbeitet mit sog. Sketches. Es ist sehr empfehlenswert für alle Funktionen eines Programms zuerst ein kleines Programm mit der entsprechenden Funktion zu schreiben und zu testen. Erst wenn alle Einzelteile funktionieren, geht man daran diese Teile zu einem komplexen Ganzen zu verbinden.

Datenfelder (Arrays)


Processing 2.0

In Variablen kann man nicht nur einzelne Werte speichern, sondern mehrere, die ein- oder zweidimensional organisiert sind. Das nennt man dann ein – oder zweidimensionale Arrays. Sie unterscheiden sich nur wenig von „normalen“ Variablen und werden in der Form array[] (eindimensional) oder array [] [] (zweidimensional) deklariert. Der Datentyp (int, float, uws.) muss außerdem festgelegt werden.

float [] eindimensional = new float [200];

Image

Die obige Anweisung deklariert ein eindimensionales Datenfeld mit dem Namen eindimensional, in dem 200 Werte gespeichert werden können (0-199). Aufgerufen werden die einzelnen Felder dann mit eindimensional[i], wobei i dann für eine Zahl von 0 bis 199 steht.

float[] [] zweidimensional = new float [200] [200];

Image

Hier haben wir ein Beispiel für ein zweidimensionales Feld, das dann mit zweidimensional[i][i] mit Werten befüllt werden kann. So können die Werte dann natürlich auch wieder ausgelesen werden.

Beispiel: starte Applet

Image

/** Copyright 2012 Thomas Koberger
*/

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

PImage foto;//deklariert die Variable foto;
float [] [] grau;

void setup() {
size(400, 400);
grau = new float [400] [400]; //hier wird ein zweidimensionales Array deklariert
foto = loadImage("blume.jpg");//weist der Variablen foto die datei blume.jpg zu
while(foto.width<1){} //pausiert den Sketch, bis das Bild geladen ist.
foto.loadPixels(); //sollte man aufrufen, bevor man die einzelen Pixel bearbeitet
//foto.pixels=sort(foto.pixels);//wird diese Funktion aktiviert, werden die Farben sortiert ausgegeben
for (int gridX = 0; gridX < foto.width; gridX++) {
for (int gridY = 0; gridY < foto.height; gridY++) {
// überträgt die Farbinfo eines Pixels auf die Variable farbe
//color farbe = foto.pixels[gridY*foto.width+gridX];
color farbe = foto.get(gridX,gridY);
// wandelt die Farbinfo in einen Grauwert um
float grauwert =red(farbe)*0.222+green(farbe)*0.707+blue(farbe)*0.071;
// println(grauwert);
grau [gridX] [gridY] = grauwert;//speichert den Grauwert in unser Array grau
}
}
}

void draw() {
background(0);
//image(foto,0,0);// gibt einen netten Überlagerungseffekt
//jetzt wird nur für jedes 10 Pixel der Helligkeitswert ausgelesen.
for (int gridX = 0; gridX < foto.width; gridX+=10) {
for (int gridY = 0; gridY < foto.height; gridY+=10) {
fill(grau [gridX] [gridY]);//gibt den Grauwert aus unserem Array aus
ellipse (gridX,gridY,9,9);
}
}
}

Dieses Beispiel liest die Farbinformationen jedes Pixels eines Bildes aus und wandelt sie in Grauwerte um. Dann werden Ellipsen mit den Grauwerten gezeichnet.

Arrays können durch Funktionen manipuliert werden.

Array-A= append(Array-A, Wert) // hängt ein Element an
Array-A= shorten(Array-A) // eliminiert das letzte Element
arrayCopy(Array-A,Array-B) // kopiert den Inhalt von Array A nach Array-B
Array-A= reverse(Array-A) // kehrt die Reihenfolge der Elemente um
Array-A= sort(Array-A) //sortiert die Elemente der Größe nach
Array-A= concat(Array-A, Array-B) // hängt Array-B an Array-A an

Aufgabe: Erweitere das folgende Programm so, dass es:

  1. ein Element an A anhängt
  2. 2 Elemente kürzt
  3. die Elemente in ein neues Array B kopiert
  4. und die Elemente in umgekehrter Reihenfolge ausgibt.
int [] A = {1, 2, 3, 4, 5, 6};
for (int i=0; i<A.length; i++) { //*.length gibt die Anzahl der Elemente eines Arrays zurück (int)
 println(A[i]);
}

Kleinsten und größten Wert eines Arrays ermitteln:

Zwei kleine Funktionen, die diese Aufgaben erledigen.

float getMaxValue ( float[] values) {
Arrays.sort(values);
return values[values.length-1];
}

Und analog dazu:

float getMinValue ( float[] values) {
Arrays.sort(values);
return values[0];
}

Rekursionen


Processing 2.0

Rekursionen sind Funktionen, die sich selbst aufrufen. D.h. auch, das eine Funktion durch sich selbst definiert wird. GNU (Gnu’s not Unix) der freien Softwarebewegung von Richard Stallman ist eine Beispiel. Man kann sich eine Rekursion auch so vorstellen: Man steht zwischen zwei Spiegeln und sieht dann unendlich viele Wiederholungen der Bilder von Vorne und von Hinten. Was man damit alles machen kann ist nicht ganz einfach zu verstehen. Dazu vielleicht ein einfaches Beispiel.

Beispiel: starte Applet
Image

/** Copyright 2012 Thomas Koberger
*/

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

void setup() {
size(300,300);
int x=width;
half_size(x);
}

void half_size (int xpos) {
if (xpos>1) {
int x= xpos*3/4;
line(x,0,x,height);
half_size(x);
}
}

In diesem Beispiel teilt die Funktion half_size eine Strecke in 3/4 und zeichnet dort eine Linie. danach ruft sich die Funktion selbst neu auf und teilt die verbleibende Strecke wieder in 2 Teile  (bei 3/4 der Länge und zeichnet eine Linie. Und so weiter, bis nur noch 1 Pixel übrig ist. Dann wird das ganze beendet.

An dem Beispiel kann man schon ganz gut erkennen, dass Rekursionen die Gefahr bergen, dass sie unendlich weiterlaufen. Das muss durch irgendeinen geeigneten Mechanismus (in diesem Beispiel die Abfrage, ob die verbleibende Strecke noch größer ist als 1) verhindert werden.

Beispiel: starte Applet
Image

/** Copyright 2012 Thomas Koberger
*/

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

void setup() {
size(300, 300);
teileBox(0, 0, 600, 400, 20); //Funktion wird einmal aufgerufen
}

void teileBox(float x, float y, float breite, float hoehe, int grau) {
fill(grau,100);
rect(x, y, breite, hoehe);
int minSeite = 100; //definiert ab welcher minimalen Breite/Höhe die Funktion stoppen soll.
if ((breite < minSeite) || (hoehe < minSeite)) {
return;
}
if (breite > hoehe) { //Wenn die Box breiter ist als hoch, dann wird sie horizontal geteilt!
float leftbreite = breite * random(0.2, 0.8);  // Breite der linken Box
teileBox(x, y, leftbreite, hoehe, grau);
teileBox(x+leftbreite, y, breite-leftbreite, hoehe, grau+40);
} else { //Wenn die Box höher ist als breit, dann wird sie vertikal geteilt!
float tophoehe = hoehe * random(0.2, 0.8);  // Höhe der oberen Box
teileBox(x, y, breite, tophoehe, grau);
teileBox(x, y+tophoehe, breite, hoehe-tophoehe, grau+40);
}
}

Dieses Beispielprogramm teilt Rechtecke je nachdem, ob sie höher oder breiter sind, horizontal und vertikal, bis eine minimale Breite bzw. Höhe erreicht ist.

Objekte und Klassen


Processing 2.0

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

Grundlegendes:

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

Beispiel: Auto starte Applet

Image

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

Klasse: Auto

class Auto {
}

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

Eigenschaften: Farbe, aktuelle Richtung,  aktuelle Position usw.

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

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

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

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

void steerLeft() {
angle+=0.1;
}

void steerRight() {
angle-=0.1;
}

void accelerate() {
speed+=0.1;
}

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

Der Konstruktor

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

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

Die gesamte Klasse Auto sieht dann so aus:

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

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

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

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

void steerLeft() {
angle+=0.1;
}

void steerRight() {
angle-=0.1;
}

void accelerate() {
speed+=0.1;
}

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

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

Hier das Hauptprogramm des fertigen Beispiels:

/** Copyright 2012 Thomas Koberger
*/

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

// Erzeugt ein Objekt der Klasse Auto
Auto Kaefer;

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

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

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

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

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

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

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

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

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

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

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

Food Futter; </em>

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

Futter=new Food();

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

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

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

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

class Animals {

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

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

Methoden:

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

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

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

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

Der Aufruf einer solchen Methode sieht dann so aus:

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

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

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

Beispiel Ameisen: starte Applet
Image


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

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

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

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

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

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

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

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

//hier beginnt die Definition des Objekts Ameise

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

class Animals {

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

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

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

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

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

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

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

}
}

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

Vektoren


 

Processing 2.0

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

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

Beispiel: starte Applet
Image

/** Copyright 2012 Thomas Koberger
*/

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

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

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

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

Das Ganze kann dann noch gut mit Zufallszahlen kombiniert werden.Image

Methoden:

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

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

Vektoren im 3 dimensonalen Raum

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

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

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

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

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

Beispiel: Rotate 2D starte Applet

Image

/** Copyright 2012 Thomas Koberger
*/

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

PVector zero, actual;

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

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

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

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

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

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

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

Die Axis-Angle Methode

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

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

Beispiel: Axis-Angle Methode für Processing 2.0

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

Image

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

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

// Im Javascript Mode auszukommentieren
PeasyCam cam;

PVector zero, left, right;

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

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

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

void draw() {
background(50);

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

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

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

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

// Differenzvektor berechnen
diff.sub(right);

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

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

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

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

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

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

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

PVector – Einführung von Daniel Shiffman

einstieg in die programmierung mit processing

Erstelle eine Website wie diese mit WordPress.com
Jetzt starten