Archiv der Kategorie: advanced

Voronoi Diagramme


Processing 2.0

Voronoi Diagramme, benannt nach dem ukrainischen Mathematiker Georgi Feodosjewitsch Woronoi können für verschiedene Problemstellungen Lösungen liefern. Z.B.:

  • Standortsuche für öffentliche Einrichtungen (optimale Erreichbarkeit)
  • Bewertung von Standorten
  • Modellierung von Nähe

Was ist nun so ein Voronoi Diagramm?

131225_122847_1010Gegeben sind eine Menge von Punkten M. Die Voronoi Region eines Punktes enthält nun ausschließlich Punkte, welche genau diesem, und keinem anderen Punkt M am nächsten sind. Es sind Gebiete mit dem gleichen nächsten Nachbarn. Das Diagramm besteht aus Voronoi Knoten, an denen immer genau 3 Voronoi Kanten einander treffen.

Aufgabe: Nimm ein leeres Blatt Papier und  zeichne ungefähr 7 zufällig verteilte Punkte darauf. Konstruiere ein Voronoi Diagramm von Hand.

Anleitung: Verbinde alle benachbarten Punkte mit Hilfslinien. Teile diese Hilfslinien in der Mitte und zeichne eine Normale durch diesen Punkt. Die Normalen stellen die Voronoi Kanten, deren Schnittpunkte die Knoten dar. Siehe auch: HowVoronoiDiagramsWork

Beispiel: Für meine Implementierung des Voronoi Algorithmus verwende ich die Mesh Library von Lee Byron. Der Sketch bietet die Möglichkeit, Voronoi Punkte zu zeichnen und zu verschieben und so mit dem Algorithmus zu experimentieren.

/** Copyright 2013 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.

/**
*
* MOUSE
* LEFT                :Create/Drag VoroPoint
*
* KEYS
* s                   : save png
*/

import java.util.Calendar;
import megamu.mesh.*;

//Arraylist and Array um die Punkte zu speichen
ArrayList<Integer> voroPoints;
Voronoi myVoronoi;
float[][] points;

//Punkt, der gerade bewegt wird
int activePoint;

void setup() {
size(800, 800);
voroPoints = new ArrayList<Integer> ();
activePoint=10000;
}

void draw() {
background(255);

//Wenn mindestens 1 Punkt vorhanden ist wird gezeichnet
if (voroPoints.size()>1) {

//getRegions
strokeWeight(1);
stroke(80, 150);
MPolygon[] myRegions = myVoronoi.getRegions();
for (int i=0; i<myRegions.length; i++)
{
// an array of points
float[][] regionCoordinates = myRegions[i].getCoords();
myRegions[i].draw(this); // draw this shape
}
}

// draw Points
fill(255);
strokeWeight(6);
stroke(0);
for (int i=0; i<voroPoints.size(); i+=2) {
point((int)voroPoints.get(i), (int)voroPoints.get(i+1));
}
}

void createVoronoi () {
points = new float[voroPoints.size()/2][2];
for (int i=0; i<voroPoints.size(); i+=2) {

points[i/2][0] =(int) voroPoints.get(i);
points[i/2][1] =(int) voroPoints.get(i+1);
}
myVoronoi = new Voronoi( points );
}

//Abfrage, ob sich an der Mausposition ein Punkt befindet
void mousePressed() {
for (int i=0; i<voroPoints.size(); i+=2) {
int x =(int) voroPoints.get(i);
int y =(int) voroPoints.get(i+1);
if (mouseX<x+5 && mouseX>x-5 && mouseY<y+5 &&mouseY>y-5) activePoint=i;
}
}

//Wenn kein Punkt aktiv ist und gerade bewegt wird, wird ein neuer hinzugefügt
void mouseReleased() {
println("released");
if (activePoint==10000) {
voroPoints.add((int)mouseX);
voroPoints.add((int)mouseY);
createVoronoi();
}
activePoint = 10000;
}

//Punkt wird gezogen
void mouseDragged() {
if (activePoint!=10000) {
voroPoints.set(activePoint, mouseX);
voroPoints.set(activePoint+1, mouseY);
createVoronoi();
}
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
}

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

Shading


Processing 2.0

In Processing 2.0 basiert die Rendering Engine auf GLSL Shadern. D.h. bei jeder Darstellung auf dem Bildschirm wird von Processing automatisch ein geeigneter Shader gewählt. Der Vorteil dabei liegt darin, dass die Programme zu einem größeren Teil von der GPU ausgeführt werden. Außerdem entfällt der Datentransfer vom RAM in den Grafikspeicher. All das führt zu einer mehr oder weniger starken Beschleunigung der Programme.

Es kommen immer 2 Shader zum Einsatz:

  • Vertex Shader
    • Manipuliert die Geometrie der dargestellten Objekte.
    • Z.B. zum Verändern der Perspektive
    • Er kann keine Objekte hinzufügen, oder entfernen!
  • Fragment Shader
    • Wandelt die 2D Objekte in ein Pixel-Array um –> Rasterization
    • Berechnet Blending und Licht
    • Das Resultat ist die endgültige Pixelfarbe

     

Wie schon gesagt, wendet Processing einen geeigneten Shader für die jeweilige Aufgabe aus. Diese Aufgaben können folgendermaßen eingeteilt werden:

  1. gefärbte Polygone ohne Licht und Textur
  2. gefärbte Polygone mit Licht, aber ohneTextur
  3. gefärbte Polygone ohne Licht, aber mit Textur
  4. gefärbte Polygone mit Licht und Textur
  5. Linien
  6. Punkte

Jeder dieser verschiedenen Shader erwartet bestimmte In- und Outputs.

Beinhaltet ein draw() Durchlauf verschiedene dieser Elemente, kommen die entsprechenden Shader (immer bestehend aus einem Vertex– und einem FragmentShader) zum Einsatz.

Will man nun einen eigenen Shader anwenden, kann man entweder beide default Shader, oder nur den Fragment-Shader ersetzen. Dadurch werden die default Shader aber nur deaktiviert und können jederzeit wieder aktiviert werden.

Beispiel: Default Shader für 1.

shader

In diesem Beispiel schauen wir uns an, wie man die default Shader von Processing durch eigene ersetzen kann und wie diese Shader aufgebaut sind.

Zuerst einmal brauchen wir einen Vertex-Shader. Dieser gleicht dem default Shader in Processing.


uniform mat4 projmodelviewMatrix;

attribute vec4 inVertex;
attribute vec4 inColor;

varying vec4 vertColor;

void main() {
gl_Position = projmodelviewMatrix * inVertex;

vertColor = inColor;
}

Was macht nun dieser Vertex Shader? Er bekommt von Processing die projmodelviewMatrix übergeben und für jeden Punkt die Variablen inVertex und inColor. In void main()  wird dann das eigentliche C-Programm platziert. In unserem Fall werden nur inVertex mit der projmodelviewMatrix verrechnet und in gl_Position gespeichert, und der Wert inColor wird in vertColor gespeichert.

Die Variable vertColor wird dann an den Fragment Shader weitergereicht und in dessen Programmteil verarbeitet. Die Definitionen am Beginn des Shaders sind aus Kompatibilitätsgründen notwendig.

Hier jetzt der gesamte Fragment Shader.

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

varying vec4 vertColor;

void main() {
// Outputting pixel color (interpolated across triangle)
gl_FragColor = vertColor;
}

Diese beiden Shader (vertex.glsl und fragment.glsl) müssen nun im Unterordner /data im Sketchordner platziert werden. Der Einsatz dieser Shader wird in Processing mit der PShader Klasse kontrolliert. Mit loadShader(„fragment.glsl“, „vertex.glsl“) werden die Shader geladen. Es kann auch nur ein Fragment Shader als Parameter übergeben werden. Ist der Shader erst einmal geladen, wird er mit shader(basic) aktiviert und mit resetShader() deaktiviert.

/** 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.
/**
* MOUSE LEFT          : Shader ein/aus
*/

PShader basic;

boolean enabled = false;

void setup() {
size(640, 360, P3D);
basic = loadShader("fragment.glsl", "vertex.glsl");
}

void draw() {
background(0);
fill(255);
if (enabled == true) {
shader(basic);
println("shader");
}

translate(width/2, height/2, -200);
sphere(200);
//println(frameRate);
}

void mousePressed() {
enabled = !enabled;
if (!enabled == true) {
resetShader();
}
}

Um zu beweisen, dass die Shader auch aktiv sind, kann man die folgende Zeile in das Programm des Fragment Shaders statt der Originalzeile einfügen:

gl_FragColor = vertColor*vec4(0.1,0.6,0.8,1.0);

Speichert man dann und lässt das Programm laufen, ändert sich die Farbe des Objekts.

Flocking


Processing 2.0

Mit Flocking Algorithmen lassen sich sehr imposante Graphiken und Animationen erstellen. Flocking Algorithmen versuchen das Verhalten von Tieren in Schwärmen nachzubilden.

Dafür erstellt man eine Klasse, die ein Individuum repräsentiert, das sich in seinen Bewegungen an den, in der Nähe befindlichen Individuen orientiert. Dabei folgt jedes Individuum einigen einfachen Regeln.

In einer einfachen Implementierung von Daniel Shiffman auf http://processing.org/learning/topics/flocking.html sind das folgende:
Image

  1. Separation (Aufteilung); wähle eine Richtung, die einer Häufung von Individuen entgegenwirkt.
    2 Individuen dürfen sich nicht am selben Ort befinden. Deshalb müssen sie einander ausweichen, wenn sie einen bestimmten Abstand unterschreiten.
  2. Alignment (Angleichung): wähle eine Richtung, die der mittleren Richtung der benachbarten Individuen entspricht.
    Jedes Individuum richtet seine Bewegungsrichtung nach den Nachbarn aus. D.h. es wird eine durchschnittl. Bewegungsrichtung der, innerhalb eines bestimmten Abstands befindlichen Individuen errechnet. Die eigenen Bewegungsrichtung passt sich dieser an.
  3. Cohesion (Zusammenhalt): wähle eine Richtung, die der mittleren Position der benachbarten Individuen entspricht.
    Alle Individuen immerhalb eines bestimmten Bereichs bewegen sich Richtung dessen Zentrum.

Diese 3 Regeln führen zu einem Verhalten, wie im untenstehenden Sketch von Daniel Shiffman. Es können bei Bedarf natürlich noch weitere Regeln definiert werden.

Starte Applet

Flock flock;

void setup() {
  size(640, 360);
  flock = new Flock();
  // Add an initial set of boids into the system
  for (int i = 0; i < 150; i++) {
    flock.addBoid(new Boid(width/2,height/2));
  }
}

void draw() {
  background(50);
  flock.run();
}

// Add a new boid into the System
void mousePressed() {
  flock.addBoid(new Boid(mouseX,mouseY));
}

// The Boid class

class Boid {

  PVector location;
  PVector velocity;
  PVector acceleration;
  float r;
  float maxforce;    // Maximum steering force
  float maxspeed;    // Maximum speed

  Boid(float x, float y) {
    acceleration = new PVector(0,0);
    velocity = new PVector(random(-1,1),random(-1,1));
    location = new PVector(x,y);
    r = 2.0;
    maxspeed = 2;
    maxforce = 0.03;
  }

  void run(ArrayList<Boid> boids) {
    flock(boids);
    update();
    borders();
    render();
  }

  void applyForce(PVector force) {
    // We could add mass here if we want A = F / M
    acceleration.add(force);
  }

  // We accumulate a new acceleration each time based on three rules
  void flock(ArrayList<Boid> boids) {
    PVector sep = separate(boids);   // Separation
    PVector ali = align(boids);      // Alignment
    PVector coh = cohesion(boids);   // Cohesion
    // Arbitrarily weight these forces
    sep.mult(1.5);
    ali.mult(1.0);
    coh.mult(1.0);
    // Add the force vectors to acceleration
    applyForce(sep);
    applyForce(ali);
    applyForce(coh);
  }

  // Method to update location
  void update() {
    // Update velocity
    velocity.add(acceleration);
    // Limit speed
    velocity.limit(maxspeed);
    location.add(velocity);
    // Reset accelertion to 0 each cycle
    acceleration.mult(0);
  }

  // A method that calculates and applies a steering force towards a target
  // STEER = DESIRED MINUS VELOCITY
  PVector seek(PVector target) {
    PVector desired = PVector.sub(target,location);  // A vector pointing from the location to the target
    // Normalize desired and scale to maximum speed
    desired.normalize();
    desired.mult(maxspeed);
    // Steering = Desired minus Velocity
    PVector steer = PVector.sub(desired,velocity);
    steer.limit(maxforce);  // Limit to maximum steering force
    return steer;
  }

  void render() {
    // Draw a triangle rotated in the direction of velocity
    float theta = velocity.heading2D() + radians(90);
    fill(200,100);
    stroke(255);
    pushMatrix();
    translate(location.x,location.y);
    rotate(theta);
    beginShape(TRIANGLES);
    vertex(0, -r*2);
    vertex(-r, r*2);
    vertex(r, r*2);
    endShape();
    popMatrix();
  }

  // Wraparound
  void borders() {
    if (location.x < -r) location.x = width+r;
    if (location.y < -r) location.y = height+r;
    if (location.x > width+r) location.x = -r;
    if (location.y > height+r) location.y = -r;
  }

  // Separation
  // Method checks for nearby boids and steers away
  PVector separate (ArrayList<Boid> boids) {
    float desiredseparation = 25.0f;
    PVector steer = new PVector(0,0,0);
    int count = 0;
    // For every boid in the system, check if it's too close
    for (Boid other : boids) {
      float d = PVector.dist(location,other.location);
      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      if ((d > 0) && (d < desiredseparation)) {
        // Calculate vector pointing away from neighbor
        PVector diff = PVector.sub(location,other.location);
        diff.normalize();
        diff.div(d);        // Weight by distance
        steer.add(diff);
        count++;            // Keep track of how many
      }
    }
    // Average -- divide by how many
    if (count > 0) {
      steer.div((float)count);
    }

    // As long as the vector is greater than 0
    if (steer.mag() > 0) {
      // Implement Reynolds: Steering = Desired - Velocity
      steer.normalize();
      steer.mult(maxspeed);
      steer.sub(velocity);
      steer.limit(maxforce);
    }
    return steer;
  }

  // Alignment
  // For every nearby boid in the system, calculate the average velocity
  PVector align (ArrayList<Boid> boids) {
    float neighbordist = 50;
    PVector sum = new PVector(0,0);
    int count = 0;
    for (Boid other : boids) {
      float d = PVector.dist(location,other.location);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(other.velocity);
        count++;
      }
    }
    if (count > 0) {
      sum.div((float)count);
      sum.normalize();
      sum.mult(maxspeed);
      PVector steer = PVector.sub(sum,velocity);
      steer.limit(maxforce);
      return steer;
    } else {
      return new PVector(0,0);
    }
  }

  // Cohesion
  // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
  PVector cohesion (ArrayList<Boid> boids) {
    float neighbordist = 50;
    PVector sum = new PVector(0,0);   // Start with empty vector to accumulate all locations
    int count = 0;
    for (Boid other : boids) {
      float d = PVector.dist(location,other.location);
      if ((d > 0) && (d < neighbordist)) {
        sum.add(other.location); // Add location
        count++;
      }
    }
    if (count > 0) {
      sum.div(count);
      return seek(sum);  // Steer towards the location
    } else {
      return new PVector(0,0);
    }
  }
}

// The Flock (a list of Boid objects)

class Flock {
  ArrayList<Boid> boids; // An ArrayList for all the boids

  Flock() {
    boids = new ArrayList<Boid>(); // Initialize the ArrayList
  }

  void run() {
    for (Boid b : boids) {
      b.run(boids);  // Passing the entire list of boids to each boid individually
    }
  }

  void addBoid(Boid b) {
    boids.add(b);
  }
}

Erläuterungen zur Boid Klasse:

  • Die Vektoren location, velocity und acceleration kontrollieren Position, Geschwindigkeit und Beschleunigung.
  • In jedem draw() Durchlauf wird die acceleration (Beschleunigung) basierend auf der 3 Kräfte Separation, Alignment und Cohesion neu berechnet.
  • Separation: Es werden alle Boids durchlaufen und ein Distanzvektor errechnet. Weiters wird aus allen Distanzvektoren, die größer 0 und kleiner als ein desiredseparation genannter Wert sind, ein Durchsschnittdistanzsvektor namens steer errechnet. Von diesem wird dann noch der aktuelle Geschwindigkeitsvektor abgezogen.
  • Alignment: Auch hier werden wieder alle Boids durchlaufen. Dabei wird ein durchschnittlicher  Geschwindigkeitsvektor aller Boids errechnet, die sich innerhalb einer bestimmten Distanz (neighbordist) befinden. Auch von diesem wird dann wieder der Boid eigene Geschwindigkeitsvektor abgezogen.
  • Cohesion: Bei der Cohesion wird ein durchschnittlicher Positionsvektor aller, sich innerhalb einer best. Distanz befindenen Boids errechnet. In diese Richtung wird dann gesteuert.
  • In der flock-Methode der Boid Klasse werden dann alle Kräfte gewichtet und zur aktuellen Beschleunigung aufsummiert.
  • In update wird dann die aktuelle Beschleunigung zur aktuellen Geschwindigkeit hinzugerechnet. Dann wird noch die Geschwindigkeit auf die Maximalgeschwindigkeit begrenzt und die Beschleunigung auf 0 gesetzt.

3D Oberflächen und Licht


Processing 2.0

Betrachtet man ein reales Objekt, z.B. einen einfärbigen Würfel, so stellt man fest, dass alle Seiten unterschiedliche Farbschattierungen aufweisen. Das liegt am Spiel zwischen Licht und Schatten. Dieses Spiel gilt es so gut, wie möglich zu immitieren, will man realitätsnahe 3D Darstellungen generieren.

Eins vorweg: Processing ist dafür nur bedingt gut geeignet. Nichts desto trotz werden wir uns hier mit den Grundlagen von Lichtern und Oberflächen beschäftigen.

Um die Effekte der Kombination aus verschieden Lichtquellen und Oberflächen zu demonstrieren habe ich das Programm lightsTesterDemo geschrieben. Die lightsTester Klasse kann auch in anderen Projekten verwendet werden.

LightsTester funktioniert leider derzeit nicht online, muss also heruntergeladen werden.

Image

Lichtquellen

Generell müssen Lichtquellen in jedem draw() Durchlauf, und nicht in setup() aufgerufen werden.

lights();

Image

Die einfachste Möglichkeit in Processing mit Licht zu arbeiten bietet die Funktion lights(). Sie generiert ein Kombination aus verschieden Lichtquellen mit default Werten.

ambientLight(rot, grün, blau);

Image

Dient als Hintergrundbeleuchtung und wird in der Regel in Kombination mit anderen Lichtquellen eingesetzt. Es beleuchtet Objekte von allen Seiten gleich stark.

directionalLight(rot, grün, blau, x, y, z);

Image

Erzeut paralleles Licht aus einer bestimmten Richtung. Trifft es auf eine Oberfläche, so entsteht Steulicht, welches wiederum auf andere Objekte wirkt. Es hat eine bestimmte Farbe und eine Richtung.

pointLight(rot, grün, blau, x, y, z); Funktioniert derzeit nicht (Processing 2.0b3)!!!

Generiert eine punktförmige Lichtquelle. Farbe und Position müssen als Parameter angegeben werden.

spotLight(rot, grün, blau, x, y, z, dx, dy, dz, winkel, fokus);

Image

Eine Lichtquelle, die wie ein Scheinwerfer funktioniert. Neben der Farbe und der Position müssen hier noch die Abstrahlrichtung (winkel) und die Konzentration der Lichtstärke im Zentrum (fokus) angegeben werden. In lights können die Werte für winkel und fokus über den alpha Kanal des entsprechenden Color Pickers gesteuert werden.

lightSpecular(rot, grün, blau);

Image

Legt die Farbe der Glanzlichter fest. Hat nur Einfluss auf die jenigen Elemente, die im Code danach erstellt werden. D.h. die Glanzlichter müssen vor den anderen Lichtern gesetzt werden.

Oberflächen

Die Effekte von Lichtquellen hängen von der Oberflächenbeschaffenheit der beleuchteten Objekte ab. Deshalb gibt es in Processing eine Reihe von Oberflächeneigenschaften, die in Kombination mit den verwendeten Lichtquellen benutzt werden können.

ambient(rot, grün, blau);

Image

Legt den Reflexionsgrad der Oberfläche in Kombination mit ambientLight() fest. Z.B. ambient(255,128,0) reflektiert rot zur Gänze, grün zur Hälfte und blau gar nicht.

emissive(rot, grün, blau);

Image

Objekte können selbst Licht emittieren. D.h. sie leuchten selbst in der hier definierten Farbe. Alledings nur dann, wenn irgendeine Lichtquelle vorhanden ist.

specular(rot, grün, blau);

Image

Damit definiert man die Farbe der Glanzlichter.

shininess(float);

Image

Legt die Glanzintensität einer Oberfläche fest.

lightFalloff(constant, linear, quadratic);

Dieser Parameter legt den Lichtabfall über die Entfernung zur Lichtquelle mit Werten zwischen 0 und 1 fest. Er wirkt allerdings nur auf point spot und ambient Lights.

Beispiel: lights

Mit dem Licht und Oberflächen Tool kannst du verschiedenste Licht und Oberflächeneffekte testen. Es funktioniert derzeit eider nur im Java Modus und muss deshalb als .zip Datei heruntergeladen werden.


// Bewege die Maus, um die Position der Lichtquelle zu ändern

//GUI Library ControlP5
import controlP5.*;
ControlP5 controlP5;
ColorPicker cplamb, cpldir, cplpoi, cpambient, cpfill, cplspt, cplspc, cpemissive, cpspecular;
CheckBox checkbox;

public int R, G, B=128;
public int shininess, falloff=0;
int mode = 0;
int [] heights = new int [25];
boolean lightsOn, ambientOn, directionalOn, pointOn, spotOn, specOn =false;

void setup() {
size(800, 800, P3D);
smooth(1);
createControlP5();

for (int i=0;i< heights.length;i++) {
heights[i]=(int) random(100, 255);
}
}

void draw() {
background(0);

// Kontrolle über die Lichter
controlP5.draw();

ambient(cpambient.getColorValue());
emissive(cpemissive.getColorValue());
specular(cpspecular.getColorValue());
shininess(shininess);
lightFalloff(0, 0, falloff);

if (specOn) {
lightSpecular(red(cplspc.getColorValue()), green(cplspc.getColorValue()), blue(cplspc.getColorValue()));
}
if (lightsOn) lights();
if (ambientOn) {
ambientLight(red(cplamb.getColorValue()), green(cplamb.getColorValue()), blue(cplamb.getColorValue()),
map(mouseX, 0, width, 1, -1), -1, map(mouseY, 0, height, 1, -1));
}
if (directionalOn) directionalLight(red(cpldir.getColorValue()), green(cpldir.getColorValue()), blue(cpldir.getColorValue()),
map(mouseX, 0, width, 1, -1), 1, map(mouseY, height, 0, -1, 1));

if (pointOn) pointLight(red(cplpoi.getColorValue()), green(cplpoi.getColorValue()), blue(cplpoi.getColorValue()),
0, 100, 0);

if (spotOn) {
spotLight(red(cplspt.getColorValue()), green(cplspt.getColorValue()), blue(cplspt.getColorValue()),
map(mouseX, 0, width, 0, width), -1000, map(mouseY, 0, height, 0, height*2),
0, 1, -1, map(alpha(cplspt.getColorValue()),0,255,0,PI), map(alpha(cplspt.getColorValue()),0,255,255,0));
}

fill(cpfill.getColorValue());
drawBlocks();
}

void drawBlocks() {

// Betrachtungsperspektive ändern
translate(100, height/2, -500);
rotateX(PI/3);

//Zeichne Blocks
for (int i=0;i<5;i++) {
for (int j=0;j<5;j++) {
pushMatrix();
translate(120*i, 120*j, heights[i*5+j]/2);
//box(50, 50, heights[i*5+j]*1.2);
sphere(heights[i*5+j]/2);
popMatrix();
}
}
}

void createControlP5() {

// Steuerelemente erstellen
controlP5 = new ControlP5(this);

//Checkboxes erstellen
checkbox = controlP5.addCheckBox("lcheckBox", 10, 10);
checkbox.setColorForeground(color(120));
checkbox.setColorActive(color(255));
checkbox.setColorLabel(color(255));
checkbox.setItemsPerRow(8);
checkbox.setSpacingColumn(119);
checkbox.addItem("lights()", 0);
checkbox.addItem("ambientLight()", 0);
checkbox.addItem("directionalLight()", 0);
checkbox.addItem("pointLight()", 0);
checkbox.addItem("spotLight()", 0);
checkbox.addItem("lightSpecular()", 0);

//Colorpicker erstellen
ControlGroup fillColor = controlP5.addGroup("fill Color", 10, 40);
fillColor.activateEvent(false);
fillColor.close();
cpfill = controlP5.addColorPicker("fillpicker", 0, 0, 128, 0);
cpfill.setGroup(fillColor);

ControlGroup ambLightCol = controlP5.addGroup("ambientLight Color", 140, 40);
ambLightCol.activateEvent(false);
ambLightCol.close();
cplamb = controlP5.addColorPicker("amblpicker", 0, 0, 128, 0);
cplamb.setGroup(ambLightCol);
cplamb.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup dirLightCol = controlP5.addGroup("directionalLight Color", 270, 40);
dirLightCol.activateEvent(false);
dirLightCol.close();
cpldir = controlP5.addColorPicker("dirlpicker", 0, 0, 128, 0);
cpldir.setGroup(dirLightCol);
cpldir.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup poiLightCol = controlP5.addGroup("pointLight Color", 400, 40);
poiLightCol.activateEvent(false);
poiLightCol.close();
cplpoi = controlP5.addColorPicker("poilpicker", 0, 0, 128, 0);
cplpoi.setGroup(poiLightCol);
cplpoi.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup sptLightCol = controlP5.addGroup("spotLight Color", 530, 40);
sptLightCol.activateEvent(false);
sptLightCol.close();
cplspt = controlP5.addColorPicker("spotlpicker", 0, 0, 128, 0);
cplspt.setGroup(sptLightCol);
cplspt.setArrayValue(new float[] {
100, 100, 100, 100
}
);

ControlGroup lightSpecCol = controlP5.addGroup("lightSpec Color", 660, 40);
lightSpecCol.activateEvent(false);
lightSpecCol.close();
cplspc = controlP5.addColorPicker("speclpicker", 0, 0, 128, 0);
cplspc.setGroup(lightSpecCol);
cplspc.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup surfaceCol = controlP5.addGroup("ambient Color", 10, 120);
surfaceCol.activateEvent(false);
surfaceCol.close();
cpambient = controlP5.addColorPicker("ambpicker", 0, 0, 128, 0);
cpambient.setGroup(surfaceCol);
cpambient.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup emissiveCol = controlP5.addGroup("emissive Color", 140, 120);
emissiveCol.activateEvent(false);
emissiveCol.close();
cpemissive = controlP5.addColorPicker("emipicker", 0, 0, 128, 0);
cpemissive.setGroup(emissiveCol);
cpemissive.setArrayValue(new float[] {
100, 100, 100, 255
}
);

ControlGroup specularCol = controlP5.addGroup("specular Color", 270, 120);
specularCol.activateEvent(false);
specularCol.close();
cpspecular = controlP5.addColorPicker("specpicker", 0, 0, 128, 0);
cpspecular.setGroup(specularCol);
cpspecular.setArrayValue(new float[] {
100, 100, 100, 255
}
);

//Slider erstellen
controlP5.begin(10, 150);
controlP5.addSlider("shininess", 0, 255, 0, 400, 110, 128, 10);
controlP5.addSlider("falloff", 0, 1, 0, 595, 110, 128, 10);
controlP5.end();
}

// Aktion Radio Button
void controlEvent(ControlEvent theEvent) {
if (theEvent.isGroup()) {
//print("got an event from "+theEvent.group().name()+"\t");
for (int i=0;i<theEvent.group().arrayValue().length;i++) {
int n = (int)theEvent.group().arrayValue()[i];
print(n);
if (i==0) {
if (n==1)  lightsOn=true;
else lightsOn=false;
}
if (i==1) {
if (n==1)  ambientOn=true;
else ambientOn=false;
}
if (i==2) {
if (n==1)  directionalOn=true;
else directionalOn=false;
}
if (i==3) {
if (n==1)  pointOn=true;
else pointOn=false;
}
if (i==4) {
if (n==1)  spotOn=true;
else spotOn=false;
}
if (i==5) {
if (n==1)  specOn=true;
else specOn=false;
}
}
}
}

3 D Basics


Processing 2.0

In Processing ist es auch möglich in 3D zu arbeiten. Dies gibt uns die Möglichkeit alle bisher programmierten Bilder und Animationen um eine Dimension zu erweitern.

Im Gegensatz zu 2D, wo wir nur die Achsen x und y haben, kommt in 3D eine z-Achse dazu, die vom Ursprung (links oben) aus direkt aus dem Bildschirm heraus zeigt.

Damit wir die 3. Dimension nutzen können, müssen wir Processing sagen, dass wir nun in 3D arbeiten wollen und einen entsprechenden Renderer wählen.

Der Einfachheit halber wurden in Processing 2 die beiden vormals zu Verfügung stehenden Renderer P3D und OPENGL zusammengeführt und die OpenGL Library in den Processing Core aufgenommen. Das hat zur Folge, dass nun auch OpenGL direkt im P3D Modus programmiert werden kann. Darüber hinaus verbessert sich auch die Kompatibilität des Processing Codes über die Plattformen hinweg verbessert.

Wir benutzen also immer den P3D Renderer:

size(600, 600, P3D);

So, und jetzt können wir (fast) alle einfachen Zeichenfunktionen von Processing auch in 3D nutzen. Wir müssen dafür allerdings immer zusätzlich zu den x-, und y-Koordinaten auch eine z-Koordinate angeben.

Einen letzten wichtigen, und manchmal etwas verwirrendenPunkt stellt die Tatsache dar, dass der Betrachter in einer 3-dimensionalen Darstellung ja auch seine Position haben muss. Um das zu verdeutlichen spielen wir mit der Funktion translate() im folgenden Beispiel.

translate(width/2,height/2, 0);

Mit dieser Zeile verschieben wir das Koordinatensystem von links oben in die Mitte des Programmfensters. Die 0 als z-Koordinate bewirkt, dass wir eine Szene so betrachten, als ob wir in 2D arbeiten würden. Wenn wir jetzt aber eine negative Zahl als z-Koordinate wählen, werden die gezeichneten Objekte kleiner, da wir den Ursprung des Koordinatensystems von uns weg bewegt haben.

Bei positiven z-Werten bewegen wir uns also weiter vor das Objekt, bei negativen weiter dahinter(bis wir es gar nicht mehr sehen können!!!)!

Beispiel Drehbares Koordinatensystem: starte Applet

Achtung: Funktioniert im Android Mode!!!

Image

void setup() {
size(640, 640, P3D);
}
void draw() {
background(0);
textSize(20);
stroke(255);
translate(width/2, height/2, -200);
println((height/2.0) / tan(PI*60.0 / 360.0));
rotateX(map(mouseY, 0, height, -PI, PI));
rotateY(map(mouseX, 0, height, -PI, PI));
drawAxes();
}

void drawAxes() {
stroke(255, 0, 0);
line(-300, 0, 0, 300, 0, 0);
text("+x", 300, 0, 0);
text("-x", -330, 0, 0);
stroke(0, 255, 0);
line(0, -300, 0, 0, 300, 0);
text("+y", 0, 330, 0);
text("-y", 0, -300, 0);
stroke(0, 0, 255);
line(0, 0, -300, 0, 0, 300);
text("+z", 0, 0, 330);
text("-z", 0, 0, -300);
}

Wenn wir das Koordinatensystem drehen fällt natürlich sofort auf, dass sich die Größe der Beschriftung mit der Entfernung von der Elemete ändert. Näheres erscheint größer, weiter entferntes kleiner. Das entspricht auch der Realität und stellt eben den großen Unterschied zu einer 2D Darstellung dar.

3 D spezifische Elemente (Körper):

Kugel

sphere(int size);

Zeichnet ein (natürlich!) 3-Dimensionale Kugel mit der Größe size. Die Kugel wird dabei immer auf den Punkt 0,0,0 zentriert.

sphereDetail(int detail);

Diese Anweisung bestimmt die Auflösung der Textur. 3D Renderer zeichnen nämlich keine Kurven, sondern zerlegen sie in einzelne Punkte, welche dann mit geraden Linien verbunden werden. Der Wert detail bestimmt die Anzahl der Punkte des Umfangs. Z.B.: detail 40 würde bedeuten, dass 360/40, also alle 9 Grad ein Punkt gezeichnet wird.

fill() und stroke() können wie gewohnt eingesetzt werden.

Beispiel Einfache Kugel: starte Applet

Achtung: Beispiel funktioniert nicht im Android Mode!!!

Im JavaScript Mode wird die Kugel nicht transparent dargestellt, und im Java Mode kommt es zu Darstellungsproblemen bei den Achsen!

Image

Quellcode siehe Applet!

Würfel:

Wie Sphere, nur mit der Anweisung box().

box(Seitenlänge);

Komplexe Formen:

Sollten komplexere Formen benötigt werden, kann man diese entweder über beginShape(), vertex(x,y,z) und endShape() in Processing erstellen, oder man importiert fertige 3D Objekte (*.obj) mit Hilfe der loadShape(*.obj) Funktion.

Beispiel 3-seitige Pyramide: starte Applet

Image

//Zeichne 3-seitige Pyramide
  beginShape(TRIANGLE);
  vertex(-1, -1, 1);
  vertex( -1, 1, 1);
  vertex( 0, 0, -1);

  vertex(-1, 1, 1);
  vertex( 1, 0, 1);
  vertex( 0, 0, -1);

  vertex(-1, -1, 1);
  vertex( 1, 0, 1);
  vertex( 0, 0, -1);

  vertex(-1, -1, 1);
  vertex( 1, 0, 1);
  vertex( -1, 1, 1);

  endShape();

Quellcode siehe Applet!

Sieht noch nicht sehr beeindruckend aus. Was noch feht ist die richtige Beleuchtung. Siehe Artikel 3D Oberflächen und Licht.

Agenten


Processing 2.0

Agenten bewegen sich entsprechend ihrer Programmierung. Hier werden sie als Objekte realisiert und bewegen sich zufällig.

Sie ändern aber nicht abrupt ihre Richtung, da wir für die Position der Agenten Vektoren verwenden, denen wir in jedem Schritt eine zufällige Abweichung addieren.

In der einfachsten Ausführung des Programms bewegen sie sich aus der Mitte heraus und prallen dann von den Rändern des Animationsfensters zurück. Dabei ist ihre Bewegung nicht gerade, sondern ändern ständig ihre Richtung.

Beispiel: einfache Agenten starte Applet

ImageImage

Agent [] agenten;

void setup() {
size(300, 300);
smooth();
stroke(255);

// hier wird ein Array für 10 Agenten erzeugt
agenten= new Agent[10];

// die Agenten selber werden erzeugt
for (int i=0; i<agenten.length; i++) {
agenten[i]=new Agent(width/2, height/2, 5);
}
}

void draw() {
// für den Spur, die die Agenten hinterlassen
fill(0, 2);
rect(0, 0, width, height);

//alle Agenten müssen die Position ändern
for (int i=0; i<agenten.length; i++) {
//jeder Agent wird gezeichnet
agenten[i].render();
//jeder Agent muß die Position ändern
agenten[i].move();
}
}
class Agent {

// Variablen
PVector position;
PVector direction;
//definiert die Stärke der Richtungsänderung
float spin = 0.20;
float radius; // 3. Radius

//der Konstruktor für die Agenten-Klasse
Agent (float theX, float theY, float aradius) {
position    = new PVector (theX, theY);
direction   = new PVector (10, 10);
direction.x = random (-1, 1);
direction.y = random (-1, 1);
radius = aradius;
}

//eine Methode
void render() {

//einkommentiert ergibt sich hieraus ein schönes Fächer-Muster
//line(position.x, position.y, width/2, height/2);
ellipse(position.x, position.y,5,5);
}

//Methode
void move() {

//die Agenten ändern ihre Richting nicht abrupt, sondern immer nur ein wenig!
direction.x += random (-spin, spin);
direction.y += random (-spin, spin);

//for a constant speed
direction.normalize();

// hier kann man die Geschwindigkeit ändern
direction.mult(1);

position.add(direction);

//damit die Agenten das Bild nicht verlassen
if (position.x < radius || position.x > (width-radius)) {
direction.x *= -1;
}
if (position.y < radius || position.y > (height-radius)) {
direction.y *= -1;
}
}
}

Beispiel: einfache Agenten2 starte Applet

Image

Hier wurde der Code um Farbe und die Linien von jedem Agenten zum Mittelpunkt erweitert.

in setup();

 colorMode(HSB, 360, 100, 100);

im Agenten selbst

 int col;

...

col=(int)random(0, 360);

...

void render() {
if (col<360) {
col+=2;
} else {
col=0;
}

//reuncomment for random color
stroke(col,100,100);

Den kompletten Source-Code findest Du beim Applet!

Beispiel: einfache Agenten3 starte Applet

Image

Hier eine Variante, bei der die Agenten miteinander durch gerade Linien verbunden werden.

Änderung in draw();

 if (i==0) {
line(agenten[i].position.x,
agenten[i].position.y,
agenten[agenten.length-1].position.x,
agenten[agenten.length-1].position.y);
}
else {
line(agenten[i].position.x, agenten[i].position.y,
agenten[i-1].position.x, agenten[i-1].position.y);
} 

Den kompletten Source-Code findest Du beim Applet!

Beispiel: einfache Agenten4 starte Applet

Image

In diesem Beispiel wird die Bewegungsfreiheit der Agenten durch die aktuelle Mausposition in x- und y- Richtung eingeschränkt. Ist der Mauszeiger in der linken oberen Ecke, versammeln sich alle Agenten im Mittelpunkt, ist er in der rechten unteren Ecke, können sie sich (fast) frei bewegen.

Dafür platzieren wir folgende Code-Zeile in setup() und in draw(). Sie bewirkt, dass der Ursprung des Koordinatensystems in die Mitte des Rahmens verschoben wird.

 translate(width/2,height/2); 

Da wir nun vom Mittelpunkt des Rahmens als Nullpunkt ausgehen, können wir mit Hilfe der map()– Funktion die aktuelle Position (x und y) von -width/2 bis +width/2 um die aktuelle Mausposition korrigieren. Das machen wir aus optischen Gründen nun für 2 Ellipsen.

 ellipse(
      map(position.x,-width/2,width/2,-mouseX/2,mouseX/2),
      map(position.y,-height/2,height/2,-mouseY/2,mouseY/2)
      ,4,4);
       fill(100,50,255);
       ellipse(
      map(position.x,-width/2,width/2,-mouseX/2,mouseX/2),

      map(position.y,-height/2,height/2,-mouseY/2,mouseY/2)
      ,3,3);

Weiters wird nun die Grenze, an der die Agenten zurückprallen vom Fensterrahmen auf einen Kreis geändert:

    if (dist(position.x,position.y,0,0)>width/2) {
          direction.x *= -1;
          direction.y *= -1;

    }

Den kompletten Source-Code findest Du beim Applet!

Beispiel: einfache Agenten5 starte Applet

Image

In diesem Beispiel arbeiten wir wieder im Color-Mode HSB.

colorMode(HSB, 360, 100, 100);

Außerdem erhält hier jeder Agent einen Index und ein Array mit den Indizes von 3 seiner nächsten Nachbarn. Damit kann man über 4 Punkte eine Kurve (curve() ) zeichnen und die Agenten damit dann Verbinden.

Variablendefinition in der Agenten-Klasse:

int [] neighbours = new int [4];
  int index;

Im Konstruktor des Agenten:

Start-Farbpunkt für jeden Agenten, damit alle gemeinsam das gesamte Spektrum abdecken.

 col=index*(360/agenten.length); 

Jeder Agent muss, wenn er erzeugt wird seine Nachbarn finden. Das macht er hiermit:

 for (int i=0;i<4;i++) {
      if (index+i>agenten.length-1) {
        neighbours[i]= index+i-agenten.length;
      }else {
      neighbours[i]= index+i;
      } 

In der Methode render des Agenten wird nun nichts mehr gezeichnet. Wir nutzen sie aber für die Veränderung der Farbwerte:

 if (col<360) {
      col+=2;
    } else {
      col=0;
    } 

Wir zeichnen die Kurve nun in draw():

curve(
      map(agenten[agenten[i].neighbours[0]].position.x,-width/2,width/2,-mouseX/2,mouseX/2),
      map(agenten[agenten[i].neighbours[0]].position.y,-height/2,height/2,-mouseY/2,mouseY/2),
      map(agenten[agenten[i].neighbours[1]].position.x,-width/2,width/2,-mouseX/2,mouseX/2),
      map(agenten[agenten[i].neighbours[1]].position.y,-height/2,height/2,-mouseY/2,mouseY/2),
      map(agenten[agenten[i].neighbours[2]].position.x,-width/2,width/2,-mouseX/2,mouseX/2),
      map(agenten[agenten[i].neighbours[2]].position.y,-height/2,height/2,-mouseY/2,mouseY/2),
      map(agenten[agenten[i].neighbours[3]].position.x,-width/2,width/2,-mouseX/2,mouseX/2),
      map(agenten[agenten[i].neighbours[3]].position.y,-height/2,height/2,-mouseY/2,mouseY/2)); 

Den kompletten Source-Code findest Du beim Applet!

Beispiel: einfache Agenten6 starte Applet

Image

Hier bekommt jeder Agent einen Startpunkt zugeordnet. In diesem Fall bilden alle Agenten gemeinsam einen Kreis. Damit jeder Agent wieder in seinen Ursprung zurückkehren kann, muss er sich seine Startposition merken. Das tut er in der PVector Variablen start.

 PVector start; 

Auch der Konstruktor der Agenten wird angepasst. Die Koordinaten, die beim Erzeugen des Agenten übertragen werden, werden in der Variable start gespeichert und die Variable position wird mit den Koordinaten 0,0 gefüllt.

 //der Konstruktor für die Agenten-Klasse
 Agent (float theX, float theY, float aradius) {
 start = new PVector (theX, theY);
 position=new PVector(0,0);
 direction = new PVector (10, 10);
 direction.x = random (-1, 1);
 direction.y = random (-1, 1);
 radius = aradius;
 }
 

Beim Zeichnen der Agenten wird nun das Koordinatensystem auf die jeweilige Startposition verschoben. Und von dort ausgehend seine Position gezeichnet.

 pushMatrix();
    translate(start.x,start.y);
      ....
  popMatrix();

Mit den folgenden Zeilen legen wir die Startpositionen der Agenten fest. Sie ordnen sich kreisförmig um dem Mittelpunkt an.

 for (int i=0; i<agenten.length; i++) {
 float x,y;
 x= width/2+(cos(map(i,0,agenten.length,0,TWO_PI))*width/4);
 y= height/2+(sin(map(i,0,agenten.length,0,TWO_PI))*height/4);
 agenten[i]=new Agent(x,y, 5);
 }
 

Den kompletten Source-Code findest Du beim Applet!

Lesen und schreiben von Textdateien


Processing 2.0

Natürlich kann man im Processing auch mit Textdateien arbeiten. Zum Einlesen einer Textdatei in ein String Array wird der Befehl loadStrings() verwendet.  Um Daten in eine Textdatei zu schreiben verwendet man saveStrings(). Auch hier muss man mit einem String Array arbeiten.

Sowohl beim Lesen, als auch beim Schreiben ist der Standard-Ordner der Ordner /data im Sketchbook-Verzeichnis. D.h. dort ist eine Datei zu platzieren, wenn man sie einlesen möchte.

Beispiel: Einlesen einer Textdatei

Damit das Beispiel funktioniert, muss eine Textdatei mit dem Namen „Textdatei.txt“ im /data Ordner des Projekts erstellt werden!!!

 Image

String[] zeilen; //String-Array

void setup() {
// hier wird der Inhalt der Textdatei in das String-Array
// zeilen eingelesen.
// jede Zeile als eignener String
zeilen = loadStrings("Textdatei.txt");
noLoop(); // damit draw() nur ein mal ausgeführt wird
}

void draw() {
println(zeilen);
}

Beispiel: Textdatei schreiben

Funktioniert auch nur mit einer Datei „Textdatei.txt“ im /data Ordner des Projekts.

String[] zeilen; //String-Array

void setup() {
// hier wird der Inhalt der Textdatei in das String-Array
// zeilen eingelesen.
// jede Zeile als eignener String

zeilen = loadStrings("Textdatei.txt");
noLoop(); // damit draw() nur ein mal ausgeführt wird
}

void draw() {
println(zeilen);
// schreibt den Inhalt von "zeilen" in eine Datei mit
// dem Namen "neueDatei.txt"
saveStrings("neueDatei.txt", zeilen);
}

Processing mit Eclipse entwickeln


Zuerst sollte man dieses Tutorial durcharbeiten: http://erik-bartmann.de/programmierung/processing/processing-mit-eclipse.html

Mit der folgenden Funktion kann man seinen Code auch als Java Application laufen lassen.

public static void main(String args[]) {
PApplet.main(new String[] { "--present", "NAMEDERKLASSE" });
}

Da Processing von PApplet abgeleitet wird, muss jede Klasse neue “ extends PApplet“ enthalten, damit alle Processing – Objekte auch innerhalb der neuen Klasse funktionieren.

Will man aber separate  Klassen erzeugen, die von einer Processing Anwendung aus benutzbar sind, haben wir das Problem, dass sich die Processing eigenen Anweisungen, wie z.B. rect() u.a. nicht verwenden lassen. Dieses Problem kann man lösen, in dem man der Klasse sagt, dass sie auf dem PApplet zeichnen soll.

Details darüber findet man hier: http://www.learningprocessing.com/tutorials/processing-in-eclipse/

Eine andere sehr gute Anleitung gibt es hier: http://creativecoding.org/en/beyond/p5/eclipse_as_editor

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.



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);
}


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