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

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:

  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.

Lichtquellen

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

lights();

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

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

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

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

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

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

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

Damit definiert man die Farbe der Glanzlichter.

shininess(float);

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!!!

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!

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

//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

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

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

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

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

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

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!!!

 

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