zurück

Spirograph -
Wie entsteht ein cooles Programm?

Wir zeichnen einen Kreis

Wir beginnen mit einer ganz einfachen Aufgabe. Wir wollen Punkte zeichnen, die auf einem Kreis liegen. Im Mathematikunterricht haben wir gelernt, dass die Koordinaten auf einer Kreisline mit Radius R durch ( Rcos(w) / Rsin(w) ) gegeben sind, wobei w der Winkel im Bogenmaß ist.

Für 70 Punkte genügt uns folgendes Programm:

kreis_1.pde

// Kreis aus Punkten

void setup() {
  size(400,400);
  frameRate(30);
}

void draw() {
  float w,x,y;    // Winkel und Koordinaten
  for (int n=0; n<70; n++) {
    w = TWO_PI*n/70;
    x = cos(w);
    y = sin(w);
    stroke(255);
    fill(0,0,160);
    ellipse(200+150*x,200-150*y,20,20);
  }
} 

So sieht das Ganze dann aus:

kreis_1

Erste Verbesserungen

Wollten wir nun etwa 100 Punkte zeichnen und dem Kreis eine andere Größe geben, müssten wir die entsprechenden Zahlen im Programm suchen und ausbessern. Und hoffen, dass wir nichts übersehen. Eine andere Größe des Fensters würde zu einem veränderten Mittelpunkt führen, wodurch auch hier viele Folgeänderungen gemacht werden müssen.

Viel besser wäre es, hätten wir Variable eingesetzt, die diese Werte enthalten. Bei kluger Wahl ihrer Namen wären die einzelnen Programmzeilen auch viel leichter verständlich (die wichtigsten Änderungen habe ich hervorgehoben):

kreis_2.pde

// Variable fuer Groesse und Punktzahl

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

void setup() {
  size(400,400);
  rMax = 150;
  x0 = width/2;
  y0 = height/2;
  num = 70;
  frameRate(30);
}

void draw() {
  float w,x,y;    // Winkel und Koordinaten
  for (int n=0; n<num; n++) {
    w = TWO_PI*n/num;
    x = cos(w);
    y = sin(w);
    stroke(255);
    fill(0,0,160);
    ellipse(x0+rMax*x,y0-rMax*y,20,20);
  }
}

Speziell die letzte Programmzeile spricht nun für sich selbst! Die Ausgabe des Programms ist natürlich unverändert.

Weitere Verbesserungen

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

kreis_3.pde

// eigene Funktion zum Zeichnen

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

void setup() {
  size(400,400);
  rMax = 150;
  x0 = width/2;
  y0 = height/2;
  num = 70;
  frameRate(30);
}

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

// Punkt (x/y) auf Bildschirmkoordinaten umrechnen und zeichnen

void plot(float xx, float yy) {
    float x = x0+rMax*xx;
    float y = y0-rMax*yy;
    
    stroke(255);
    fill(0,0,160);
    ellipse(x,y,20,20);
}

Das sieht noch klarer aus! Wir haben zwar deutlich mehr Zeilen tippen müssen, aber der Text ist klar strukturiert, leicht lesbar und verständlich. Und was für uns besonders wichtig ist: nachträgliche Änderungen und Erweiterungen lassen sich ganz leicht einbauen.

Eine interessantere Funktion

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

kreis_4.pde


// einen Kreis auf dem Kreis rotieren lassen

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

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

void setup() {
  size(400,400);
  rMax = 150;
  x0 = width/2;
  y0 = height/2;
  num = 70;      // versuche 300;
  
  r2 = 1.5;
  f2 = 3;        // versuche 6,7,..,-5
  frameRate(30);
}

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

// Punkt (x/y) auf Bildschirmkoordinaten umrechnen
// und zeichnen
void plot(float xx, float yy) {
  float rSize = rMax/(1+r2);  // Vergroesserungsfaktor
  float x = x0+rSize*xx;
  float y = y0-rSize*yy;
    
  stroke(255);
  fill(0,0,160);
  ellipse(x,y,20,20);
} 
	 

Das ergibt folgendes Bild

kreis_4

 

Eine GUI zur Steuerung

Eine sehr einfache und praktische Möglichkeit der interaktiven Steuerung von Parametern ist die Bibliothek controlP5, die im Internet (auch über die Processing-Homepage) erhältlich ist. Man bekommt eine kleine gepackte Datei, die im Processing-Verzeichnis ins Verzeichnis 'libraries' gehört. Dazu erstellen wir dort ein neues Verzeichnis namens 'controlP5' und entpacken den Inhalt der zip-Datei dort hinein.

Mit Hilfe dieser Bibliothek erzeugen wir einen Schieberegler für die Frequenz f2 des kleinen Kreises. Es genügt, dem Slider den Namen der Variable f2 anzugeben, schon ist die Variable ferngesteuert. Die Syntax lautet:

addSlider("variable", minimalWert, maximalWert, posX, posY, groesseX, groesseY)

kreis_5.pde

// viele Punkte berechnen und einen 
// Schieberegler fuer die Frequenz des zweiten Kreises

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

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

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

void setup() {
  size(400+100,400);  // links Platz fuer den Regler schaffen
  rMax = 150;
  x0 = (width-100)/2+100;    // 100 Pixel Platz für Regler
  y0 = height/2;
  num = 400;
  
  r2 = 0.5;
  f2 = 3;
  frameRate(30);
  
  steuerung = new ControlP5(this);
  Slider s1 = steuerung.addSlider("f2",-20,20,  20,20, 10,200); 
}

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

// Punkt (x/y) auf Bildschirmkoordinaten umrechnen und zeichnen
void plot(float xx, float yy) {
  float rSize = rMax/(1+abs(r2));  // Vergroesserungsfaktor
  float x = x0+rSize*xx;
  float y = y0-rSize*yy;
    
  stroke(255);
  fill(0,0,160);
  ellipse(x,y,20,20);
}

Das Programmfenster sieht nun so aus:

kreis_5

Links ist der Regler zu sehen. controlP5 ist nicht sehr konfigurierbar, aber ganz leicht anzuwenden.

Weitere Ideen

Nun kann man immer mehr nette Ideen verwirklichen. Lade Dir die Quelltexte aller Sketche herunter und vollziehe sie schrittweise nach - es ist nicht schwierig!

Die Lininen müssen nicht unbedingt vom Nachbarpunkt aus gezeichnet werden - die Variable 'prev' (previous) bestimmt, der wievielte Punkt als Nachbar angesehen werden soll. Wir erhalten damit interessante Grafiken, die an die Fadengrafiken des GZ-Unterrichts erinnern. Mit hübschen Schiebereglern lässt sich eine Unmenge von Mustern erzeugen:

kreis_9.pde

kreis_9.pde

Wir lassen aus einer kleinen Idee ein immer größeres und mächtigeres Programm entstehen!

Mit Animation!

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

kreis_11.pde


// Regler fuer Drehung, Zeichenstil

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

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

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

float phase=0;
float tempo=0.02;  // Geschwindigkeit der Drehung

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

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

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

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

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


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

Dieses Programm sieht so aus:

kreis_11

Hier kann man schon sehr viel einstellen und ändern.

Du kannst das Programm weiter ausbauen - bis zu einem Programm, das großartige Grafiken nach unterschiedlichen Methoden erzeugt. Details im Unterricht!

Zum Schluss

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

kreis_demo.pde