Point Cloud


Processing 2.0

Der Kinect Sensor bietet die einzigartige Möglichkeit die Distanz der einzelnen Bildpunkte zur Kamera zu messen. Wir nutzen diese Möglichkeit jetzt und zeichnen alle einzelnen Bildpunkte, die der Sensor liefert einfach im 3-dimensionalen Raum darzustellen. Das ganze bezeichnet man als Point Cloud.

141104_113108_52

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

import SimpleOpenNI.*;
import java.util.Calendar;

SimpleOpenNI kinect;

void setup() {
  size(1200, 768, P3D);
  kinect = new SimpleOpenNI(this);
  kinect.enableDepth();
  stroke(255);
}

void draw() {
  background(0);
  kinect.update();

  // Verschiebe die Szene in den Mittelpunkt des Fensters
  translate(width/2, height/2, -500);
  // vertikal drehen, damit die Szene nicht auf dem Kopf steht
  rotateX(radians(180));
  // Rotationspunkt in die Mitte der Szene verschieben

  translate(0, 0, 1500);
  randomSeed(20);
  // Damit die Szene automatisch rotiert.
  rotateY((float)frameCount/50);
  translate(0, 0, -1500);

  // Einkommentieren, wenn man die Szene per Maus rotieren will!
  //  rotateY(map(mouseX, 0, width, -PI, PI));
  //  rotateX(map(mouseY, 0, width, -PI, PI));

  // Hier liefert die Kinect ein Array mit Vektoren
  PVector[] depthPoints = kinect.depthMapRealWorld();

  // Wir zeichnen nicht jeden Punkt, um die Sache zu beschleunigen
  for (int i = 0; i < depthPoints.length; i+= (int)random(2, 10)) {
    PVector currentPoint = depthPoints[i];
    stroke(map(currentPoint.z, 0, 7000, 255, 80));
    point(currentPoint.x, currentPoint.y, currentPoint.z);
  }
}

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

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

Farbe

Nun können wir auch beide Kameras der Kinect nutzen und den Punkten ihre entsprechende Farbe zuweisen. Dabei kommen die Tiefeninformationen von der IR und die Farbinformationen von der RGB Kamera. Die dafür notwendigen mathematischen Operationen, nämlich das Auffinden der korrespondierenden Punkte der beiden Kameras, erledigt die Kinect.

141107_164425_59

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

import SimpleOpenNI.*;
import java.util.Calendar;

SimpleOpenNI kinect;

void setup() {
  size(1200, 768, P3D);
  kinect = new SimpleOpenNI(this);
  kinect.enableDepth();
  // Aktiviert die RGB Kamera der Kinect
  kinect.enableRGB();
  //Berechnet die korresponierenden Punkte der beiden Kameras
  //Dadurch bekommen die korrespondierenden Punkte der Tiefen- und der RGB Kamera
  //die gleichen Indizes
  kinect.alternativeViewPointDepthToImage();
}

void draw() {
  background(0);
  kinect.update();

  // Verschiebe die Szene in den Mittelpunkt des Fensters
  translate(width/2, height/2, -500);
  // vertikal drehen, damit die Szene nicht auf dem Kopf steht
  rotateX(radians(180));
  // Rotationspunkt in die Mitte der Szene verschieben

  translate(0, 0, 1500);
 
  // Damit die Szene automatisch rotiert.
  rotateY((float)frameCount/50);
  translate(0, 0, -1500);

  // Einkommentieren, wenn man die Szene per Maus rotieren will!
  //  rotateY(map(mouseX, 0, width, -PI, PI));
  //  rotateX(map(mouseY, 0, width, -PI, PI));

  // Hier liefert die Kinect ein Array mit Vektoren
  PVector[] depthPoints = kinect.depthMapRealWorld();
  PImage rgbImage = kinect.rgbImage();
 
  // Wir zeichnen nicht jeden Punkt, um die Sache zu beschleunigen
  for (int i = 0; i < depthPoints.length; i+= 10) {
    PVector currentPoint = depthPoints[i];
    
    // Hiermit färben wir die Punkte der Tiefeninformation mit der entsprechend
    // Farbe aus der RGB Kamera ein.
    stroke(rgbImage.pixels[i],map(currentPoint.z,0,7000,255,80));
    point(currentPoint.x, currentPoint.y, currentPoint.z);
  }
  println(frameRate);
}

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

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

Linien

Nun kann man die einzelnen Punkte auch mit Linien verbinden.

 

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

import SimpleOpenNI.*;
import java.util.Calendar;

SimpleOpenNI kinect;

void setup() {
  size(1200, 768, P3D);
  kinect = new SimpleOpenNI(this);
  kinect.enableDepth();
  // Aktiviert die RGB Kamera der Kinect
  kinect.enableRGB();
  //Berechnet die korresponierenden Punkte der beiden Kameras
  //Dadurch bekommen die korrespondierenden Punkte der Tiefen- und der RGB Kamera
  //die gleichen Indizes
  kinect.alternativeViewPointDepthToImage();
  noSmooth();
}

void draw() {
  background(0);
  kinect.update();

  // Verschiebe die Szene in den Mittelpunkt des Fensters
  translate(width/2, height/2, -500);
  // vertikal drehen, damit die Szene nicht auf dem Kopf steht
  rotateX(radians(180));
  // Rotationspunkt in die Mitte der Szene verschieben

  translate(0, 0, 1500);

  // Damit die Szene automatisch rotiert.
  rotateY((float)frameCount/50);
  translate(0, 0, -1500);

  // Einkommentieren, wenn man die Szene per Maus rotieren will!
  //  rotateY(map(mouseX, 0, width, -PI, PI));
  //  rotateX(map(mouseY, 0, width, -PI, PI));

  // Hier liefert die Kinect ein Array mit Vektoren
  PVector[] depthPoints = kinect.depthMapRealWorld();
  PImage rgbImage = kinect.rgbImage();
  strokeWeight(1);
  PVector prevPoint, currentPoint;
  prevPoint = new PVector(0, 0, 0);
  // Wir zeichnen nicht jeden Punkt, um die Sache zu beschleunigen
  for (int i = 20; i < depthPoints.length; i+= 3) {

    currentPoint = depthPoints[i];

    stroke(rgbImage.pixels[i]); 

    if (prevPoint.x!=0 && currentPoint.x!=0) {
      line(currentPoint.x, currentPoint.y, currentPoint.z, prevPoint.x, prevPoint.y, prevPoint.z);
    }
    
    // Wie speichern die Koordinaten des aktuellen Punkts als letzten Punkt
    prevPoint = currentPoint;
  }
  println(frameRate);
}

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

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

Kinect und Processing


Habe heute den Kinect Sensor bekommen. In Java kann man ihn mit verschiedenen Bibliotheken nutzen. Z.B. mit der Simple-OpenNI Bibliothek, welche die vielfältigsten Möglichkeiten bietet. Die Simple OpenNI ist ein OpenNI- und NITE Wrapper für Processing. Die Installation unter Win7 laut dieser Anleitung ist sehr einfach. Dann kann man den ersten Processing Code laufen lassen.

141103_145804_933
/** Copyright 2012 Thomas Koberger
 */
// https://lernprocessing.wordpress.com/
// Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
// 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 &quot;AS IS&quot; 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.

import SimpleOpenNI.*;
import java.util.*;
SimpleOpenNI kinect;

void setup()
{
  size(640*2, 480);
  kinect = new SimpleOpenNI(this);
  kinect.enableDepth();
  kinect.enableIR();
}

void draw()
{
  kinect.update();
  image(kinect.depthImage(), 0, 0);
  image(kinect.irImage(), 640, 0);
  println(frameRate);
}

void keyReleased() {
  if (key == DELETE || key == BACKSPACE) background(360);
  if (key == 's' || key == 'S') saveFrame(timestamp()+&quot;_##.png&quot;);
}  


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


Was also kann der Kinect Sensor leisten?

Er liefert neben dem RGB Bild ein, durch einen IR-Emitter und einen IR-Sensor erzeugtes, Tiefenbild der Umgebung (eine DepthMap).  Das versetzt uns als Programmierer in die Lage, die Position von Objekten festzustellen und darauf zu reagieren. Der Kinect Sensor kann also die Lage von Objekten im Raum erkennen. Die Bilder haben eine Auflösung von 640 x 480 Pixel.

Die Technik hat allerdings noch ein paar Einschränkungen. Erstens funktioniert der Tiefensensor erst ab einer Distanz von ~50 cm. Zweitens werfen Objekte im Vordergrund natürlich einen Schatten. Für im Schatten liegende Objekte ist natürlich keine Tiefen-Information verfügbar.

Eine kleine Rolle in diesem Zusammenhang spielt bei diesen Überlegungen auch, dass die RGB- und Tiefenkamera in einem Abstand von einigen cm ihren Blick auf die Welt offenbaren.

Ubuntu 12.04 laut Anleitung installiert.

Die Geburt der Sonne


Processing 2.0

Hierbei handelt es sich um eine Kombination der Techniken Additive Blending und Flocking. Es werden Partikel erzeugt, die sich dann in der Mitte verdichten.

Der Sketch wäre vielleicht in Processing 2.x auch mit anderen Techniken zu realisieren. Ich habe ihn aber nur von der Version 1.5 portiert.

/** Copyright 2014 Thomas Koberger
 */
// based on a flocking algorithm by Daniel Shiffman
// 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.

import processing.opengl.*;
import peasy.*;
import javax.media.opengl.*;
import javax.media.opengl.GL2;
import java.util.*;

// Flock
Flock flock;
Stars stars;

GL2 gl; 
PGraphicsOpenGL pgl;

PVector l[];
float str;

void setup() {
 size(1280, 720, OPENGL);

 // Create Flock
 flock = new Flock();
 stars = new Stars(400);

 for (int x = 0; x < 350; x+=1) {
 flock.addBoid(new Boid(new PVector(random(-500, 500), random(-500, 500)), random(5.0, 2), 0.5, 10000));
 Boid b = (Boid) flock.boids.get(flock.boids.size()-1);
 b.desiredseparation=random(3, 20);
 }

 for (int x = 0; x < 15; x+=1) {
 flock.addBoid(new Boid(new PVector(random(-500, 500), random(-500, 500)), random(29.0, 2), 0.5, 10000));
 Boid b = (Boid) flock.boids.get(flock.boids.size()-1);
 b.desiredseparation=random(20, 50);
 }
}

void draw() {
 hint(DISABLE_DEPTH_TEST);
 fill(0, 15);
 rect(-width, -height, width*2, height*2);
 translate(width/2, height/2, 300);

 pgl = (PGraphicsOpenGL) g; // g may change
 gl = ((PJOGL)beginPGL()).gl.getGL2();

 gl.glEnable( GL.GL_BLEND ); 
 gl.glEnable(GL.GL_LINE_SMOOTH); 

 // This fixes the overlap issue
 gl.glDisable(GL.GL_DEPTH_TEST);

 // Define the blend mode
 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);

 stars.drawStars(gl);
 flock.run(gl);

 gl.glDisable(GL.GL_BLEND);
 endPGL();
 if (frameCount%100==1) println("Rate: "+frameRate);
 //saveFrame("line-####.jpg");
}

void keyReleased() {
 //if (key == DELETE || key == BACKSPACE) background(360);
 if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.jpg");
} 

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

class Boid {

 PVector loc;
 PVector vel;
 PVector acc;

 float maxforce; // Maximum steering force
 float maxspeed; // Maximum speed
 float maxVertspeed; // Maximum speed vertical
 float maxVertforce; // Maximum speed vertical
 int lifeTime;

 float desiredseparation;// Distance to separate from neighbours
 float neighbordistAlgn; // Distance to align with neighbours
 float neighbordist; // Distance to stick to neighbours
 float desiredAvoidDist; // Distance to avoid Avoid Hunters

 float r, g, b, alpha;

 Boid(PVector l, float ms, float mf, int lt) {
 acc = new PVector(0, 0, 0 );
 vel = new PVector(0, 0, 0);
 loc = l.get();
 r = 2.0;
 maxspeed = ms;
 maxforce = mf;
 lifeTime= lt;
 maxVertspeed=ms*3;

 desiredseparation = 30.0;
 neighbordistAlgn = 15.0;
 neighbordist = 40.0;
 desiredAvoidDist = 300;
 }

 void run(ArrayList boids, GL2 gl) {
 flock(boids, gl);
 update();
 render(gl);
 lifeTime-=1;
 }

 // We accumulate a new acceleration each time based on three rules
 void flock(ArrayList boids, GL2 gl) {
 PVector sep = separate(boids); // Separation
 PVector ali = align(boids); // Alignment
 PVector coh = cohesion(boids, gl); // Cohesion
 PVector target = target(); // Food
 // Arbitrarily weight these forces
 sep.mult(2);
 ali.mult(0.5);
 coh.mult(1.0);
 target.mult(1.0);
 // Add the force vectors to acceleration
 acc.add(sep);
 acc.add(ali);
 acc.add(coh);
 acc.add(target);
 }

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

 void render(GL2 gl) {
 PVector modelOrientation = new PVector(0, 0, 1); 
 PVector heading=new PVector(vel.x, vel.y, vel.z);
 heading.mult(2);

 if (PVector.dist(loc, new PVector(0, 0, 0))>70) {
 int lines=5;
 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);

 for (int i=lines;i>0;i-=4) {
 gl.glLineWidth(i);
 gl.glColor4f(float(1-i/lines), 0.5-i/lines, 0.2+i/lines, 
 alpha/i);
 gl.glBegin(GL.GL_LINES); 
 gl.glVertex2f(loc.x-heading.x, loc.y-heading.y);
 gl.glVertex2f(loc.x, loc.y);
 gl.glEnd();
 }
 }
 }

 void seek(PVector target) {
 acc.add(steer(target, false));
 }

 void arrive(PVector target) {
 acc.add(steer(target, true));
 }

 // A method that calculates a steering vector towards a target
 // Takes a second argument, if true, it slows down as it approaches the target
 PVector steer(PVector target, boolean slowdown) {
 PVector steer; // The steering vector
 PVector desired = target.sub(target, loc); // A vector pointing from the location to the target
 float d = desired.mag(); // Distance from the target is the magnitude of the vector
 // If the distance is greater than 0, calc steering (otherwise return zero vector)
 if (d > 0) {
 // Normalize desired
 desired.normalize();
 // Two options for desired vector magnitude (1 -- based on distance, 2 -- maxspeed)
 if ((slowdown) && (d < 100.0)) desired.mult(maxspeed*(d/100.0)); // This damping is somewhat arbitrary
 else desired.mult(maxspeed);
 // Steering = Desired minus Velocity
 steer = target.sub(desired, vel);
 steer.limit(maxforce); // Limit to maximum steering force
 } 
 else {
 steer = new PVector(0, 0, 0);
 }
 return steer;
 }

 // Separation
 // Method checks for nearby boids and steers away
 PVector separate (ArrayList boids) {
 PVector steer = new PVector(0, 0, 0);
 int count = 0;
 // For every boid in the system, check if it's too close
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = PVector.dist(loc, other.loc);

 //verändert, damit der Scwarm besser zusammenhält
 // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
 if ((d > 0) && (d < desiredseparation/3)) {
 // Calculate vector pointing away from neighbor
 PVector diff = PVector.sub(loc, other.loc);
 diff.normalize();
 diff.div(d*10); // Weight by distance
 steer.add(diff);
 count++; // Keep track of how many
 }

 if ((d > 0) && (d < desiredseparation)) {
 // Calculate vector pointing away from neighbor
 PVector diff = PVector.sub(loc, other.loc);
 diff.normalize();
 diff.div(d/16); // 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(vel);
 steer.limit(maxforce);
 }
 return steer;
 }

 // Alignment
 // For every nearby boid in the system, calculate the average velocity
 PVector align (ArrayList boids) {
 PVector steer = new PVector(0, 0, 0);
 int count = 0;
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = PVector.dist(loc, other.loc);
 if ((d > 0) && (d < neighbordistAlgn)) {
 steer.add(other.vel);
 count++;
 }
 }
 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(vel);
 steer.limit(maxforce);
 }
 return steer;
 }

 // Cohesion
 // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
 PVector cohesion (ArrayList boids, GL2 gl) {
 PVector sum = new PVector(0, 0, 0); // Start with empty vector to accumulate all locations
 int count = 0;

 gl.glEnable( GL.GL_BLEND ); 
 gl.glColor4f(0.95, 0.3, 0.2, 0.007);
 gl.glLineWidth(6);
 gl.glBegin(GL2.GL_POLYGON); 
 gl.glVertex2f(loc.x, loc.y);
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = loc.dist(other.loc); 

 if ((d > 0) && (d < neighbordist)) {
 sum.add(other.loc); // Add location
 count++;

 gl.glVertex2f(other.loc.x, other.loc.y);
 }
 }
 gl.glDisable( GL.GL_BLEND );
 gl.glEnd();

 // für Farbverdichtungen
 alpha = map(count, 0, 50, 0, 0.9);

 if (count > 0) {
 sum.div((float)count);
 return steer(sum, true); // Steer towards the location
 }
 return sum;
 }

 // Move Towards Target
 PVector target () {
 PVector sum = new PVector(0, 0, 0); // Start with empty vector to accumulate all locations
 //sum.limit(maxforce);
 return steer(sum, true); // Steer towards the location
 }

 boolean alive() {
 if (lifeTime<0) return false;
 else return true;
 }
}

// The Flock (a list of Boid objects)

class Flock {
 ArrayList boids; // An arraylist for all the boids

 Flock() {
 boids = new ArrayList(); // Initialize the arraylist
 }

 void run(ArrayList hunters, GL gl) {
 for (int i = 0; i < boids.size(); i++) {
 Boid b = (Boid) boids.get(i);
 }
 }
 void run(GL2 gl) {

 for (int i = 0; i < boids.size(); i++) {
 Boid b = (Boid) boids.get(i); 

 b.run(boids, gl); // Passing the entire list of boids to each boid individually

 if (!b.alive()) boids.remove(i);
 }
 }


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

// The Boid class

class Stars {

 PVector [] stars; 

 Stars(int amt) {

 stars = new PVector [amt];
 
 for (int i=0; i < amt; i++) {
 stars[i] = new PVector (random(-width/2, width/2), random(-height/2, height/2), 0);
 }
 }

 void drawStars(GL2 gl) {
 for (int i=0; i<stars.length;i++) {
 if(frameCount%5==0){
 gl.glColor4f(1, 1, 1, random(0.02, 0.2));
 float w=random(0.3, 1.2);
 gl.glLineWidth(w*2);
 gl.glBegin(GL.GL_LINES); 
 gl.glVertex3f(stars[i].x-w/2, stars[i].y, stars[i].z);
 gl.glVertex3f(stars[i].x+w/2, stars[i].y, stars[i].z);
 gl.glVertex3f(stars[i].x, stars[i].y-w/2, stars[i].z);
 gl.glVertex3f(stars[i].x, stars[i].y+w/2, stars[i].z);
 gl.glEnd();
 }
 }
 }
}


 

Polarlichter


Processing 2.0

Hierbei handelt es sich um eine Kombination der Techniken Additive Blending und Flocking. Es werden an zufälligen Koordinaten Partikelschwärme erzeugt, deren Partikel dann mit Linien verbunden werden. Diese werden dann additiv übereinander geblendet und ergeben farbliche Verdichtungen, die mich an Polarlichter erinnern. Außerdem werden im Hintergrund Sterne gezeichnet.

Der Sketch wäre vielleicht in Processing 2.x auch mit anderen Techniken zu realisieren. Ich habe ihn aber nur von der Version 1.5 portiert.

/** Copyright 2014 Thomas Koberger
 */
// based on a flocking algorithm by Daniel Shiffman
// 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.

import processing.opengl.*;
import javax.media.opengl.*;
import javax.media.opengl.GL2;
import java.util.*;

Flock flock;
Stars stars;

//Über diese Objekte kann man auf OPENGL features zugreifen
GL2 gl;
PGraphicsOpenGL pgl;

PVector l[];
float str;

void setup() {
 size(1280, 720, OPENGL);

 //Sterne und Schwarm erzeugen
 flock = new Flock();
 stars = new Stars(350);
}

void draw() {
 //Verhindert, dass Objekte am Schirm, die von anderen verdeckt werden nicht gezeichnet werden
 hint(DISABLE_DEPTH_TEST);
 fill(0, 20);
 rect(-width, -height, width*2, height*2);
 translate (width/2, height/2, -200);

 // OpenGL Object Setup
 pgl = (PGraphicsOpenGL) g; // g may change
 gl = ((PJOGL)beginPGL()).gl.getGL2();
 gl.glEnable( GL.GL_BLEND );
 gl.glEnable(GL.GL_LINE_SMOOTH); 

 // This fixes the overlap issue
 gl.glDisable(GL.GL_DEPTH_TEST);

 // Define the blend mode
 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);

 //zeichne Sterne
 stars.drawStars(gl);

 if (frameCount%6==0) {
 //Add an initial set of boids into the system
 float x=random(-width/1.3, width/1.3);
 float y=random(-height/1.3, height/1.3);
 for (int i =(int) random(5,25); i > 0; i-=1) {
 flock.addBoid(new Boid(new PVector(x, y, 0), 10.0, 0.1, 300));
 }
 } 

 flock.run(gl);
 gl.glDisable(GL.GL_BLEND);
 endPGL();
 if (frameCount%100==1) println("Rate: "+frameRate);

 //einkommentieren, wenn man die Frames speichern will
 //saveFrame(timestamp()+"_##.jpg");
}

void mousePressed() {
 //fügt neue Boids hinzu
 if ( mouseButton==LEFT) {
 for (int x = 0; x < 30; x+=1) {
 flock.addBoid(new Boid(new PVector(mouseX-width*2/3, mouseY-height*2/3), 10.0, 0.1, 500));
 }
 }
}

void keyReleased() {
 //if (key == DELETE || key == BACKSPACE) background(360);
 if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.jpg");
} 

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

// The Boid class
// Original by Danial Shiffman modified by Thomas Koberger

class Boid {

 PVector loc;
 PVector vel;
 PVector acc;
 //float r;
 float maxforce; // Maximum steering force
 float maxspeed; // Maximum speed
 float maxVertspeed; // Maximum speed vertical
 float maxVertforce; // Maximum speed vertical
 int lifeTime;

 float desiredseparation;// Distance to separate from neighbours
 float neighbordistAlgn; // Distance to align with neighbours
 float neighbordist; // Distance to stick to neighbours
 float desiredAvoidDist; // Distance to avoid Avoid Hunters

 float r, g, b, alpha; 

 Boid(PVector l, float ms, float mf, int lt) {
 acc = new PVector(0, 0, 0 );
 vel = new PVector(0, 0, 0);
 loc = l.get();
 r = 2.0;
 maxspeed = ms;
 maxforce = mf;

 //definiert eine Lebensspanne für die einzelnen Boids
 lifeTime= lt;

 //definiert die Abstände, innerhalb derer sich die einzelnen Boids gegenseitig beeinflussen
 maxVertspeed=ms*3;
 desiredseparation = 8.0;
 neighbordistAlgn = 100.0;
 neighbordist = 60.0;
 }

 void run(ArrayList boids, GL2 gl) {
 flock(boids, gl);
 update();
 //borders();
 render(gl);
 lifeTime-=1;
 }

 // We accumulate a new acceleration each time based on three rules
 void flock(ArrayList boids, GL2 gl) {
 PVector sep = separate(boids); // Separation
 PVector ali = align(boids); // Alignment
 PVector coh = cohesion(boids, gl); // Cohesion
 PVector target = target(); // Food
 // Arbitrarily weight these forces
 sep.mult(2);
 ali.mult(1.0);
 coh.mult(0.1);
 target.mult(0.6);
 // Add the force vectors to acceleration
 acc.add(sep);
 acc.add(ali);
 acc.add(coh);
 acc.add(target);
 }

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

 //Zeichne die Boids
 void render(GL2 gl) {
 PVector modelOrientation = new PVector(0, 0, 1);
 PVector heading=new PVector(vel.x, vel.y, vel.z);
 heading.mult(2);

 int lines=1;
 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
 for (int i=lines;i>0;i-=4) {
 gl.glLineWidth(i);
 gl.glColor4f(1-alpha*i/lines, 0.5-alpha*i/lines, alpha*i/lines,
 alpha/8);
 gl.glBegin(GL.GL_LINES);
 gl.glVertex2f(loc.x-heading.x, loc.y-heading.y);
 gl.glVertex2f(loc.x, loc.y);
 gl.glEnd();
 }
 }

 void seek(PVector target) {
 acc.add(steer(target, false));
 }

 void arrive(PVector target) {
 acc.add(steer(target, true));
 }

 // A method that calculates a steering vector towards a target
 // Takes a second argument, if true, it slows down as it approaches the target
 PVector steer(PVector target, boolean slowdown) {
 PVector steer; // The steering vector
 PVector desired = target.sub(target, loc); // A vector pointing from the location to the target
 float d = desired.mag(); // Distance from the target is the magnitude of the vector
 // If the distance is greater than 0, calc steering (otherwise return zero vector)
 if (d > 0) {
 // Normalize desired
 desired.normalize();
 // Two options for desired vector magnitude (1 -- based on distance, 2 -- maxspeed)
 if ((slowdown) && (d < 100.0)) desired.mult(maxspeed*(d/100.0)); // This damping is somewhat arbitrary
 else desired.mult(maxspeed);
 // Steering = Desired minus Velocity
 steer = target.sub(desired, vel);
 steer.limit(maxforce); // Limit to maximum steering force
 }
 else {
 steer = new PVector(0, 0, 0);
 }
 return steer;
 }

 // Separation
 // Method checks for nearby boids and steers away
 PVector separate (ArrayList boids) {
 PVector steer = new PVector(0, 0, 0);
 int count = 0;
 // For every boid in the system, check if it's too close
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = PVector.dist(loc, other.loc);

 //verändert, damit der Scwarm besser zusammenhält
 // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
 if ((d > 0) && (d < desiredseparation/3)) {
 // Calculate vector pointing away from neighbor
 PVector diff = PVector.sub(loc, other.loc);
 diff.normalize();
 diff.div(d*10); // Weight by distance
 steer.add(diff);
 count++; // Keep track of how many
 }

 if ((d > 0) && (d < desiredseparation)) {
 // Calculate vector pointing away from neighbor
 PVector diff = PVector.sub(loc, other.loc);
 diff.normalize();
 diff.div(d/16); // 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(vel);
 steer.limit(maxforce);
 }
 return steer;
 }

 // Alignment
 // For every nearby boid in the system, calculate the average velocity
 PVector align (ArrayList boids) {
 PVector steer = new PVector(0, 0, 0);
 int count = 0;
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = PVector.dist(loc, other.loc);
 if ((d > 0) && (d < neighbordistAlgn)) {
 steer.add(other.vel);
 count++;
 }
 }
 if (count > 0) {
 steer.div((float)count);
 }
 //definiert den Rot Wert
 r=map(steer.mag(), 0, 10, 0, 0.9);
 // 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(vel);
 steer.limit(maxforce);
 }
 return steer;
 }

 // Cohesion
 // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
 PVector cohesion (ArrayList boids, GL2 gl) {
 PVector sum = new PVector(0, 0, 0); // Start with empty vector to accumulate all locations
 int count = 0;

 // Define the blend mode
 gl.glEnable( GL.GL_BLEND );
 gl.glColor4f(r, g, b, 0.004);
 gl.glLineWidth(8);
 gl.glBegin(GL2.GL_POLYGON);
 gl.glVertex2f(loc.x, loc.y);
 for (int i = 0 ; i < boids.size(); i++) {
 Boid other = (Boid) boids.get(i);
 float d = loc.dist(other.loc);

 //definiert den Blau und Grün Wert
 b=map(d, 0, neighbordist*12, 0, 0.9);
 g=map(vel.mag(), 0, 10, 0, 0.9);

 if ((d > 0) && (d < neighbordist)) {
 sum.add(other.loc); // Add location
 count++;

 gl.glVertex2f(other.loc.x, other.loc.y);
 }
 }
 gl.glEnd();

 // für Farbverdichtungen
 alpha = map(count, 0, 50, 0, 1);

 if (count > 0) {
 sum.div((float)count);
 return steer(sum, true); // Steer towards the location
 }
 return sum;
 }

 // Move Towards Target
 PVector target () {
 PVector sum = new PVector(0, 0, 0); // Start with empty vector to accumulate all locations
 return steer(sum, true); // Steer towards the location
 }

 boolean alive() {
 if (lifeTime<0) return false;
 else return true;
 }
}

// The Flock (a list of Boid objects)

class Flock {
 ArrayList boids; // An arraylist for all the boids

 Flock() {
 boids = new ArrayList(); // Initialize the arraylist
 }

 void run(ArrayList hunters) {
 for (int i = 0; i < boids.size(); i++) {
 Boid b = (Boid) boids.get(i);
 }
 }
 void run(GL2 gl) {
 for (int i = 0; i < boids.size(); i++) {
 Boid b = (Boid) boids.get(i);
 b.run(boids, gl); // Passing the entire list of boids to each boid individually

 //Elimiert Boids, wenn sie den sichbaren bereich verlassen
 if (!b.alive() || b.loc.x>1200 || b.loc.x<-1200 || b.loc.y>1200 || b.loc.y<-1200) {
 boids.remove(i);
 }
 }
 }

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

// The Boid class

class Stars {

 PVector [] stars; 

 Stars(int amt) {

 stars = new PVector [amt];

 for (int i=0; i < amt; i++) {
 stars[i] = new PVector (random(-width, width), random(-height, height), 0);
 }
 }

 void drawStars(GL2 gl) {
 for (int i=0; i<stars.length;i++) {
 if(frameCount%5==0){
 gl.glColor4f(1, 1, 1, random(0.02, 0.2));
 float w=random(0.3, 1.2);
 gl.glLineWidth(w*2);
 gl.glBegin(GL.GL_LINES);
 gl.glVertex3f(stars[i].x-w/2, stars[i].y, stars[i].z);
 gl.glVertex3f(stars[i].x+w/2, stars[i].y, stars[i].z);
 gl.glVertex3f(stars[i].x, stars[i].y-w/2, stars[i].z);
 gl.glVertex3f(stars[i].x, stars[i].y+w/2, stars[i].z);
 gl.glEnd();
 }
 }
 }
}

 

Voronoi und die französiche Revolution


Processing 2.0

Hier nun 2 Anwendungsbeispiele für Voronoi Diagramme zum Thema „französische Revolution“.
Eine Erklärung, wie so ein Diagramm funktioniert findest du hier. Im ersten Beispiel wird eine Library verwendet, mit der man Schriftzüge in einzelne Punkte auflösen kann. Die Geomerative Library. Infos dazu gibts hier.

Beispiel 1: Hier werden die Schriftzüge der Werte Liberté  Égalité  Fraternité (Freiheit, Gleichheit, Brüderlichkeit) zerlegt, in Voronoi Bereiche aufgespalten und dann entsprechend eingefärbt. Blau für die Freiheit, Weiß für Gleichheit und Rot für Brüderlichkeit.

140109_082800_01

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

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

RShape shp, shp1, shp2;
RPoint[] pnts, pnts1;
PImage qr;

String BspText1 = "Liberté  Égalité  Fraternité";

ArrayList<Integer> voroPoints;
Voronoi myVoronoi;
float[][] points;

void setup() {
size(4000, 1000);
voroPoints = new ArrayList<Integer> ();

translate(width/2, height*2/12);

RG.init(this);
// 3 Shape - Objekte werden erzeugt.
// Die Schrift mit dem Namen "Ubuntu-R.ttf" muss im data Ordner platziert werden
shp1 = RG.getText(BspText1, "Ubuntu-R.ttf", width*8/120, CENTER);

// Punkte an der Schriftkontur finden
//Abstand der Punkte
RCommand.setSegmentLength (3);
//Modus
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
//RCommand.setSegmentator(RCommand.ADAPTATIVE);

// Die Shapes werdem gezeichnet und positioneiert
shp1.translate(0, height*7/12);
fill(180, 160);

// Finden der Konturpunkte
pnts = shp1.getPoints();

// Variation der einzelnen Punkte
for (int i=0;i<pnts.length; i+=1) {
stroke(255, 0, 0);
point(pnts[i].x, pnts[i].y);

int var=(int) random(-2, 2);
int x = (int) pnts[i].x+var;
var=(int) random(-2, 2);
int y = (int) pnts[i].y+var;

//Damit nicht 2 Punkte die gleichen Koordinaten haben
boolean coordAvailable=true;
for (int j=0; j<voroPoints.size(); j+=2) {
if (((int)voroPoints.get(j)==x && (int)voroPoints.get(j+1)==y)
|| x==0 || y==0) {
coordAvailable=false;
}
}

if (coordAvailable) {
voroPoints.add(x);
voroPoints.add(y);
}
else println("sameCoord");
}
println("NumPoints: "+voroPoints.size());
createVoronoi () ;
qr =loadImage("qrcode.png");
}

void draw() {
background(255);
translate(width/2, 0);

//get and draw VoroRegions
MPolygon[] myRegions = myVoronoi.getRegions();

for (int i=0; i<myRegions.length; i++) {
// an array of points
float[][] regionCoordinates = myRegions[i].getCoords();

int col= (int)random(0, 3);
if (col==0) fill(0, 0, 255);
else if (col==1) fill(255, 40);
else fill(255, 0, 0);
stroke(80, 50);
strokeWeight(1);
myRegions[i].draw(this); // draw this shape
}

//draw Points
strokeWeight(2);
stroke(80, 150);
for (int i=0; i<voroPoints.size(); i+=2) {
point((int)voroPoints.get(i), (int)voroPoints.get(i+1));
}
image(qr, width/2-height/5, height-height/5, height/10, height/10);
saveFrame(timestamp()+"_##.png");
}

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

//Hierbei muss die Art des Objekts in der ArrayList festgelegt werden.
points[i/2][0] =(int) voroPoints.get(i);
points[i/2][1] =(int) voroPoints.get(i+1);
}
myVoronoi = new Voronoi( points );
}

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

Beispiel 2: Visualisierung der Werte Liberté  Égalité  Fraternité (Freiheit, Gleichheit, Brüderlichkeit) in Form von  Voronoi Diagrammen.Fertig

/** 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
*
* KEYS
* Toggle Mode
* 0                   : Freiheit
* 1                   : Gleichheit
* 1                   : Brüderlichkeit
* s                   : save png
*/

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

ArrayList<Integer> voroPoints;
Voronoi myVoronoi;
float[][] points;

//Freiheit = 0
//Gleichheit = 1
//Brüderlichkeit = 2
int mode=0;

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

void draw() {
createVoronoi();
if (mode==0) {
fill(0, 0, 190, 255);
stroke(200);
}
else if (mode==1) {
fill(255);
stroke(80);
}
else {
fill(220, 0, 0);
stroke(200);
}
if (voroPoints.size()>1) {

//getRegions
strokeWeight(1);
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
strokeWeight(6);
for (int i=0; i<voroPoints.size(); i+=2) {
point((int)voroPoints.get(i), (int)voroPoints.get(i+1));
}
}

void createFreiheit() {
voroPoints = new ArrayList<Integer> ();

for (int i=160; i<width-80;i+=80) {
for (int j=160; j<height-80;j+=80) {

if (i==400 && j ==400) {
voroPoints.add(i+(int)random(-40, 40));
voroPoints.add(j+(int)random(-40, 40));
}
else {
voroPoints.add(i);
voroPoints.add(j);
}
}
}
}

void createGleichheit() {
voroPoints = new ArrayList<Integer> ();

for (int i=160; i<width-80;i+=80) {
for (int j=160; j<height-80;j+=80) {
voroPoints.add(i);
voroPoints.add(j);
}
}
}

void createBruederlichkeit() {
voroPoints = new ArrayList<Integer> ();

for (int i=160; i<width-80;i+=80) {
for (int j=160; j<height-80;j+=80) {
if (i==400 && j ==400) {
voroPoints.add(i);
voroPoints.add(j+20);
} else if (i==400 && j ==480) {
voroPoints.add(i);
voroPoints.add(j-20);
}else {
voroPoints.add(i);
voroPoints.add(j);
}
}
}
}

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

void keyReleased() {
if (key == DELETE || key == BACKSPACE) background(360);
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
if (key == '0') {
mode=0;
createFreiheit();
}
if (key == '1') {
mode=1;
createGleichheit();
}
if (key == '2') {
mode=2;
createBruederlichkeit();
}
loop();
}

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

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

Wahlergebnis


Processing 2.0

Unser Ziel war es die Wahlergebnisse 2013 möglichst einfach, informativ und ohne prozentuelle Zahlenwerte, visuell darzustellen.

Am Anfang hatten wir mehrere Lösungsvorschläge gesammelt, aber haben uns schließlich für eine Darstellung mithilfe von Kreisen entschlossen. Ein Kreis in der Mitte wird von mehreren Kreisen umschlossen, wobei in vier Ebenen (Österreich, Bundesländer, Bezirke, Gemeinden) die gesamte Verteilung der Stimmen, auf die bei der Wahl angetretenen Parteien abgebildet wird.

Um einen guten visuellen Effekt zu erzeugen haben wir die Radien der Kreise immer so vergrößert dass diese den gleichen Flächeninhalt wie der Kreis in der Mitte haben. Diesen Effekt erhält man indem man den Radius des ersten Kreises (in der Mitte) mit dem Faktor multipliziert, so erhält man den Radius des zweiten Kreises. Den Radius des dritten Kreises erhält man dann indem man den Radius des ersten Kreises mit multipliziert usw…

Die Kreise haben wir dann dem Prozentsatz nach, den die Parteien bei den Wahlen erzielt haben in Sektoren unterteilt und eingefärbt. Ausgegangen sind wir hier von den gültigen Stimmen.

wahl13klein wahlklein

Nationalratswahl 2008.pdf

Nationalratswahl 2013.pdf

// WahlVisualisierung Nationalratswahl 2008
//
// Copyright 2013 Thomas Koberger
//
// http://www.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 im Pdf Mode gerendert werden kann
//Nötig, um die geforderte Auflösung zu erreichen
import processing.pdf.*;

//Array zum Einlesen der csv Datei
String [][] csv;
PImage fo;
//Array enthält die Parteifarben
color[] parteiFarbe = {
color(255, 0, 0), //SPÖ
color(0, 0, 0), //ÖVP
color(0, 0, 255), //FPÖ
color(255, 180, 0), //BZÖ
color(0, 255, 0), //Grüne
color(255, 255, 0), //Frank
color(255, 0, 100), //Neos
color(190, 0, 0), //KPÖ
color(255, 0, 255), //Pirat
color(0, 100, 255), //CPÖ
color(0, 100, 255), //Wandl
color(0, 100, 255), //M
color(0, 100, 255), //EUAUS
color(0, 100, 255), //SLP
color(0, 100, 255)
};

//Array enthält die Parteinamen
String [] parteien= {
"SPÖ",
"ÖVP",
"FPÖ",
"BZÖ",
"Grüne",
"Frank",
"Neos",
"KPÖ",
"Pirat",
"CPÖ",
"Wandl",
"M",
"EUAUS",
"SLP"
};

//Variable bestimmt später die Größe der Grafik
float weight;

float angle;

void setup() {
size(8000, 8000, PDF, "Wahl13.pdf");

//Definitionen
strokeWeight(1);
stroke(100);
ellipseMode(CENTER);
textFont(createFont("Tahoma", 96));
textAlign(CENTER, CENTER);
imageMode(CENTER);

//folgender Code ist für den Import der Daten aus
//aus einer .svg zuständig und stammt im Original von:
//for importing csv files into a 2d array
String lines[] = loadStrings("NRW13.csv");
int csvWidth=0;
//Berechnet die Anzahl der Spalten in der csv Datei
for (int i=0; i < lines.length; i++) {
//println(i+". "+lines[i]);
String [] chars=split(lines[i], ';');
if (chars.length>csvWidth) {
csvWidth=chars.length;
}
}
//Erstellt ein Array basierend auf die Anzahl der Zeilen und Spalten der csv Datei
csv = new String [lines.length][csvWidth];

//parse values into 2d array
for (int i=0; i < lines.length; i++) {
String [] temp = new String [lines.length];
temp= split(lines[i], ';');
for (int j=0; j < temp.length; j++) {
csv[i][j]=temp[j];
}
}
//Berechnet einen Wert, der die Größe der Grafik bestimmt,
//und aus der Anzahl der Stimmberechtigten ermittelt wird
weight = sqrt(parseFloat(csv[0][6])/PI)*2;
angle=0;
}

void draw () {
background(255);
pushMatrix();
translate(width/2, height/2);
drawBeschriftung();
drawLaender();
drawAustria();
popMatrix();
textSize(50);
textAlign(CENTER, CENTER);
//text("Quelle: http://www.bmi.gv.at/cms/BMI_wahlen/nationalrat/2008/files/Ergebnis_end.zip", width/2, height-height/50);
// Exit the program
fo =loadImage("qrcode.png");
image(fo,width/1.08,height/1.08,640,640);
println("Finished.");
exit();
}

void drawLaender() {
pushMatrix();
float weightBl=weight*sqrt(3);

for (int i=0; i < csv.length; i++) {

//draw Bundesländer
String Gkz=csv[i][0];
Gkz=Gkz.substring(1, 6);
//println(parseInt(Gkz)%10000);
if (parseInt(Gkz)%10000==0 && parseInt(Gkz)/10000!=0) {
float percentage= parseFloat(csv[i][6])/parseFloat(csv[0][6])*100;
float angleBl=radians(percentage*3.6);
int gkzBl = parseInt(Gkz)/10000;
int partyBl=getStrongestParty(i);
println("Bl: "+gkzBl);

//draw Bezirke
float weightBz=weight*sqrt(5);
pushMatrix();
for (int k=0; k < csv.length; k++) {
Gkz=csv[k][0];
Gkz=Gkz.substring(1, 6);
//println(Gkz+" 11: "+parseInt(Gkz));

if (parseInt(Gkz.substring(0, 1))==gkzBl && (parseInt(Gkz)-gkzBl*10000)%100 ==0 && (parseInt(Gkz)-gkzBl*10000) >0 ) {
float percentageBz= parseFloat(csv[k][6])/parseFloat(csv[0][6])*100;
float angleBz=radians(percentageBz*3.6);
int gkzBz = parseInt(Gkz.substring(1,3));
println(gkzBz+" Bz: " + csv[k][1]+ " gkz: "+csv[k][0]);
int partyBz=getStrongestParty(k);

//draw Gemeinden
float weightGmd=weight*sqrt(7);
pushMatrix();
for (int l=0; l < csv.length; l++) {
Gkz=csv[l][0];
Gkz=Gkz.substring(1, 6);

if (parseInt(Gkz.substring(0,1))==gkzBl && parseInt(Gkz.substring(1,3))==gkzBz && parseInt(Gkz.substring(3,5))!=0) {
println(gkzBl+" gkzBz: "+gkzBz+" Gmd: "+Gkz.substring(3,5)+" "+csv[l][1]+ " gkz: "+csv[l][0]);
float percentageGmd= parseFloat(csv[l][6])/parseFloat(csv[0][6])*100;
//println(parseFloat(csv[k][2])+" percBZ"+percentageBz+" perc"+percentage);
float angleGmd=radians(percentageGmd*3.6);
int gkzGmd = parseInt(csv[l][0]);
int partyGmd=getStrongestParty(l);
//println(gkzGmd);

//Zeichnet die Stimmverteilung der Gemeinden
drawParty( angleGmd, l, 7);
//Zeichnet die Beschriftung der Gemeinden
angle+=angleGmd/2;
rotate(angleGmd/2);
fill(100);
pushMatrix();
translate(weightGmd/2+10, 0);
if (degrees(angle)>90 && degrees(angle)<270) {
rotate(PI);
textAlign(RIGHT, CENTER);
}
else {
textAlign(LEFT, CENTER);
}
textSize(map(min(parseFloat(csv[l][6]),10000),100,15000,5,16));
fill(parteiFarbe[partyGmd]);
text(csv[l][1], 0, 0);
popMatrix();
rotate(angleGmd/2);
angle+=angleGmd/2;
//println(csv[k][0]+" : "+angle);
}
}
popMatrix();
angle-=angleBz;
//Zeichnet die Stimmverteilung der Bezirke
drawParty( angleBz, k, 5);
//Zeichnet die Beschriftung der Bezirke
angle+=angleBz/2;
rotate(angleBz/2);
fill(100);
pushMatrix();
translate(weightBz/2+10, 0);
if (degrees(angle)>90 && degrees(angle)<270) {
rotate(PI);
textAlign(RIGHT, CENTER);
}
else {
textAlign(LEFT, CENTER);
}

textSize(weight/160);
fill(parteiFarbe[partyBz]);
text(csv[k][1], 0, 0);
popMatrix();
angle+=angleBz/2;
rotate(angleBz/2);
}
}
popMatrix();
angle-=angleBl;
//Zeichnet die Stimmverteilung der Bundesländer
println(gkzBl+" angle: "+angleBl);
drawParty( angleBl, i, 3);
//Zeichnet die Beschriftung der Bezirke
angle+=angleBl/2;
rotate(angleBl/2);
fill(100);
pushMatrix();
translate(weightBl/2+weight/50, 0);
rotate(PI/2);
textSize(weight/45);
textAlign(CENTER, CENTER);
fill(parteiFarbe[partyBl]);
text(csv[i][1], 0, 0);
popMatrix();
rotate(angleBl/2);
angle+=angleBl/2;
}
}

popMatrix();
fill(255);
ellipse(0, 0, weight*sqrt(2), weight*sqrt(2));
}

void drawAustria() {
//Zeichnet den Inneren Kreis mit dem Ergebnis für ganz Österreich
pushMatrix();
angle=0;
//Liest für jede Partei die Stimmen in % aus
for (int i=0;i<26; i+=2) {
//Stimmen für die jeweilige Partei in %
float percentage=parseFloat(csv[0][i+8]);
//Winkel des Kreissektors jeder Partei
float angleAt=radians(percentage*3.6);
//Zeichnet den Kreissektor in der Farbe der Partei
stroke(200);
fill(255);
arc(0, 0, weight*sqrt(2), weight*sqrt(2), 0, angleAt, PIE);
fill(parteiFarbe[i/2]);
//Zeichnet den Kreissektor für die Beschriftung
noStroke();
arc(0, 0, weight, weight, 0, angleAt, PIE);
//Beschriftung der Kreissektoren
rotate(angleAt/2);
angle+=angleAt/2;
fill(100);
pushMatrix();
translate(weight/2+weight/100, 0);
if (degrees(angle)>90 && degrees(angle)<270) {
rotate(PI);
textAlign(RIGHT, CENTER);
}
else {
textAlign(LEFT, CENTER);
}
textSize(weight/40);
//if (percentage>0.5) text(parteien[i/2]+" "+csv[0][i+8]+"%", 0, 0);
if (percentage>0.5) text(parteien[i/2], 0, 0);
popMatrix();
angle+=angleAt/2;
rotate(angleAt/2);
}
popMatrix();
}

//Zeichnet die einzelnen Kreissektoren der Parteien für jeden Wahlkreis
void drawParty(float winkel, int i, int ebene) {
stroke(200);
fill(255);
arc(0, 0, weight*sqrt(ebene+1), weight*sqrt(ebene+1), 0, winkel, PIE);
noStroke();
float offset=0;
for (int j=0;j<26; j+=2) {
float breite=parseFloat(csv[i][j+8]);
fill(parteiFarbe[j/2]);
arc(0, 0, weight*sqrt(ebene-offset), weight*sqrt(ebene-offset), 0, winkel, PIE);
offset+=parseFloat(csv[i][j+8])/100;
}
}

int getStrongestParty(int index) {
int party=0;
float strongestParty=0;
for (int m=0;m<26; m+=2) {
//println(csv[i][1]+" :"+parseFloat(csv[i][m+8])+" strong:"+strongestParty+" bl: "+partyBl);
if (strongestParty<parseFloat(csv[index][m+8])) {
strongestParty=parseFloat(csv[index][m+8]);
party=m/2;
}
}
return(party);
//println(csv[i][1]+" :"+partyBl);
}

void drawBeschriftung() {
pushMatrix();
String ueberschrift= "Nationalratswahl 2013";
println(weight*sqrt(9)-weight*sqrt(8));
//textFont(createFont("Tahoma", (int)(weight*sqrt(9)-weight*sqrt(8))));

textSize((weight*sqrt(9)-weight*sqrt(8))/2);
textAlign(CENTER);
//translate(width/2,height/2);

fill(255, 0, 0);
ellipse(0, 0, weight*sqrt(9), weight*sqrt(9));
fill(255);
ellipse(0, 0, weight*sqrt(9)-(weight*sqrt(9)-weight*sqrt(8))/3, weight*sqrt(9)-(weight*sqrt(9)-weight*sqrt(8))/3);
fill(255, 0, 0);
ellipse(0, 0, weight*sqrt(9)-(weight*sqrt(9)-weight*sqrt(8))*2/3, weight*sqrt(9)-(weight*sqrt(9)-weight*sqrt(8))*2/3);
rotate(PI*7/6);
fill(0);
arc(0, 0, weight*sqrt(9), weight*sqrt(9), 0, PI*2/3, PIE);
fill(250);
ellipse(0, 0, weight*sqrt(8), weight*sqrt(8));

for (int i=0;i<ueberschrift.length();i++) {
rotate(PI*2/3/(ueberschrift.length())/2);
pushMatrix();
translate(weight*sqrt(8)/2+weight/100, 0);
rotate(PI/2);
fill(255);
text(ueberschrift.charAt(i), 0, 0);
popMatrix();
rotate(PI*2/3/(ueberschrift.length())/2);
}
popMatrix();
}

Kinect Einstieg


Die Kinect ist eine 3D Kamera. Sie nimmt in erster Linie nicht das Aussehen von Objekten auf, sondern deren Position im Raum.

Wie schafft die Kinect das?

Die Hardware besteht aus 2 Kameras. Einer normale RGB Kamera und einer Infrarot Kamera. Die RGB Kamera liefert ein Bild vergleichbar einer günstigen Webcam in einer Auflösung von 640 mal 480 Pixel. Die IR Kamera liefert ein IR Bild in der selben Auflösung. Außerdem ist die Kinect mit einem IR Projektor ausgestattet. Dieser projeziert Infrarot Pixel, die dann mit den entsprechenden Pixel der IR Kamera verrechnet werden. Daraus kann dann die Entfernung der Pixel vom Sensor errechnet werden.

121212_222007_369

IR Bild aus der Kinect

Die Kinect kann nun verschiedene Outputs produzieren. Dafür verwenden wir folgendes Beispielprogramm:

/** 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
*
* KEYS
* s                   : save png
*/

import SimpleOpenNI.*;
import java.util.Calendar;

SimpleOpenNI  kinect;

void setup()
{
size(640, 480);

kinect = new SimpleOpenNI(this);

// depthMap aktivieren
kinect.enableDepth();

// RGB Kamera aktivieren
//kinect.enableRGB();

// IR Kamera aktivieren
//kinect.enableIR();

// Scenemap aktivieren
//kinect.enableScene();
}

void draw()
{
// Update Kinect
kinect.update();

// Eine der vier Images können aktiviert werden, wenn auch in
// setup() die entsprechende Funktion aktiviert wurde
// Achtung: RGB und IR können nicht parallel genutzt werden!!!
image(kinect.depthImage(), 0, 0);
//image(kinect.rgbImage(),0,0);
//image(kinect.irImage(), 0, 0);
//image(kinect.sceneImage(),0,0);
}

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

Das Programm erzeugt zuerst einmal ein Objekt des SimpleOpenNI Sensors namens kinect. Mit dessen Hilfe kann man nun die Funktionen der Kinect kontrollieren. Damit wir dann Output generieren können, müssen wir die verschiedenen Funktionen der Kinect aktivieren. Das passiert in der Funktion setup(). In draw() müssen dann die Daten aus dem Sensor abergerufen werden. Nun können die vom Sensor bereitgestellten Bilder ausgegeben werden.

Wie sind diese Bilder zu interpretieren?

1. depthImage()

121212_221759_2334

Dieses Bild wird von der Kinect aus den Tiefeninformationen generiert. Nahe Objekte werden heller dargestellt, als weiter entfernte. Sind Objekte weniger weit als etwa 50 cm funktioniert die Entfernungsmessung nicht. Diese Objekte werden schwarz dargestellt. Der Messbereich reicht bis 8m. Die Entfernung werden von der Kinect sehr genau gemessen. Wir werden später sehen, wie wir diese ermitteln können. Was an  dem Bild noch auffällt ist, dass es kleine scharze Bereiche enthält. Diese werden von Schatten verursacht, die näher gelegene Objekte auf weiter entfernte werfen. In diesen Bereichen kann natürlich keine Tiefeninfo abgerufen werden.

Wenn wir das Programm um folgende Zeile erweitern, können wir die Tiefeninformation an der Mausposition abrufen. Dafür verwenden wir die OpenNI Funktion depthMap(). Sie gibt ein int- Array mit der Entfernung des Punktes zum Sensor zurück.

  println(kinect.depthMap()[640*mouseY+mouseX]);

2. rgbImage()

121212_221837_865

Dieses Bild stammt von der RGB Kamera und ist qualitativ nicht besonders ansprechend. Allerdings ist sie doch recht interessant. Wir werden uns später ansehen, wie man die Farbinfos auf die Tiefenprojektion übertragen und somit ein farbiges 3 dimensionales Bild erstellen können.

3. irImage()

121212_222007_369

Output der IR Kamera. Hier kann man sehr gut die projezierten IR Punkte sehen.

4. sceneImage()

121212_224833_15438

Hier sind wir schon bei den höheren Funktionen der SimpleOpenNI. In diesem Modus versucht die Library Gegenstände im Bild zu erkennen. Wird ein Gegenstand erkannt, färbt ihn das System automatisch ein.

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.

Rotations III


Eine weitere Variation der Rotations.

/** Copyright 2012 Thomas Koberger
*/

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

int rand=(int) random(0, 50);

void setup() {
size(1280, 1280);
background(0);
stroke(50, 100, 200, 5);
frameRate(50);
colorMode(HSB);
}

void draw() {
if (frameCount<=500) {
translate(width/2, height/2);
strokeWeight(frameCount/2);
float winkel=radians(frameCount*80);
rotate(winkel*rand);
stroke(map(radians(winkel)%(2*PI), 0, 2*PI, 0, 255), 255, 255, 5);
translate(cos(winkel)*100, sin(winkel)*100);
line(100, 0, 300, 0);
if (frameCount%50 == 0) saveFrame("rotations-"+rand+"-#####.png");
}
}


Rotations II


Processing 2.0

Beispiel: Das Auge

Das Programm von Rotations I wird nun um eine zufällig generierte Farbpalette erweitert. Diese Zufallsfarben weisen eine Gauss’sche Normalverteilung auf. Siehe Zufall

Außerdem ändert sich während der Laufzeit die Strichlänge, Farbe und Position.

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

Random generator;
int str=(int) random(7, 7);

int col[] = new int [str];
int baseCol= (int) random(0, 255);

void setup() {
size(2700, 2700);
background(0);
frameRate(120);
colorMode(HSB);
generator = new Random();
for (int i =0; i<col.length; i++) {
float gauss = (float) generator.nextGaussian();
col[i] = round( 40 * gauss + baseCol);
}
println(col);
}

void draw() {
if (frameCount<30000) {
translate(width/2, height/2);
//strokeWeight(frameCount/128);
strokeWeight(random(1, width/40));
rotate(radians(frameCount/2*(360/str)));
// println(str);
stroke(col[frameCount%str]-frameCount/200, 255, 255, 1);
line(width/4.5-frameCount/50, 0, random(width/8, width*4/10)-frameCount/50, 0);
if (frameCount%3000==0) saveFrame("rotate"+str+baseCol+"#####.png");
}
}

Rotations I


Processing 2.0

Ausgehend von einem Beispiel aus dem Artikel Transformationen hat mich interessiert, was mit einfachen Rotationen alles möglich ist.

Beispiel: Erst mal einfärbig und mit nur einer Rotation

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(900,900);
background(0);
stroke(50,100,200,5);
frameRate(25);
}

void draw() {
if(frameCount<500) {
translate(width/2,height/2);
strokeWeight(frameCount/4);
rotate(radians(frameCount*20));
line(50,0,350,0);
}
if(frameCount==500) saveFrame("rotate.png");
}

Beispiel: Etwas mehr Farbe

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(900,900);
background(0);
stroke(50,100,200,5);
frameRate(25);
colorMode(HSB);
}

void draw() {
if(frameCount<500) {
translate(width/2,height/2);
strokeWeight(frameCount/4);
println(radians(frameCount*20));
rotate(radians(frameCount*20));
stroke(map(radians(frameCount*20)%(2*PI),0,2*PI,0,255),255,255,5);
line(50,0,350,0);
}
if(frameCount==500) saveFrame("rotate.png");
}

Beispiel: Variation mit etwas schmäleren Strichen und mehr Wiederholungen

/** 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(900, 900);
background(255);
stroke(50, 100, 200, 5);
frameRate(120);
colorMode(HSB);
}

void draw() {
if (frameCount<50000) {
translate(width/2, height/2);
strokeWeight(frameCount/64);
rotate(radians(frameCount*20));
stroke(map(radians(frameCount*20)%(2*PI), 0, 2*PI, 0, 255), 255, 255, 5);
line(200, 0, 220, 0);
if (frameCount%500 == 0) saveFrame("background-######.png");
}
}

The Hunt


Processing 2.0

Image

Für dieses Projekt habe ich den Flocking Algorithmus von Daniel Shiffman um eine Dimension erweitert um damit eine 3 dimensionale Szene erzeugen zu können. Darin gibt es Schwarmfische und Haie.

  • Die Schwarmfische bewegen sich nach den Regeln aus dem Flocking Artikel und 2 zusätzlichen Regeln. Sie bewegen sich immer zum Ursprung des Koordinatensystems und weichen den Haien aus.
  • Die Haie bewegen sich nach einem reduzierten Satz von Regeln. Sie halten einen bestimmten Abstand zueinander und sie bewegen sich in Richtung der durchschnittlichen Position der Schwarmfische in ihrer Nähe.

Die 3D Modelle der Fische stammen von http://www.turbosquid.com/ . Ich habe dann die Szene noch entsprechend ausgeleuchtet (–> siehe Artikel 3D Oberflächen und Licht).

Download Source

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

OpenCV Gesichtserkennung


Processing 2.0

Die Gesichtserkennung ist eine sehr gefragte Funktion von OpenCV. Sie beruht auf einem Verfahren, das mit Beschreibungsdateien arbeitet. Diese Dateien werden aus vielen (hunderten oder tausenden von) Bildern errechnet und als .xml Dateien abgespeichert. OpenCV bringt schon einige dieser Beschreibungsdateien mit. Vereinzelt findet man sie auch im Internet: Beschreibungsdateien.

Hier die offizielle Dokumentation.

In OpenCV 2.3 bereits inkludierte Dateien  (in Ubuntu 12.04 zu finden in: /usr/share/opencv/haarcascades/):

  • haarcascade_eye_tree_eyeglasses.xml
  • haarcascade_frontalface_alt.xml
  • haarcascade_lowerbody.xml
  • haarcascade_mcs_mouth.xml
  • haarcascade_profileface.xml
  • haarcascade_eye.xml
  • haarcascade_frontalface_default.xml
  • haarcascade_mcs_eyepair_big.xml
  • haarcascade_mcs_nose.xml
  • haarcascade_righteye_2splits.xml
  • haarcascade_frontalface_alt2.xml
  • haarcascade_fullbody.xml
  • haarcascade_mcs_eyepair_small.xml
  • haarcascade_mcs_righteye.xml
  • haarcascade_upperbody.xml
  • haarcascade_frontalface_alt_tree.xml
  • haarcascade_lefteye_2splits.xml
  • haarcascade_mcs_lefteye.xml
  • haarcascade_mcs_upperbody.xml

Eine gut Einführung, wie das ganze funktioniert hier auf englisch von Kyle Mcdonald.

1. Beschreibungsdatei laden:

Eine dieser Beschreibungsdateien wird im setup() geladen:

opencv.cascade(„/usr/share/opencv/haarcascades/“,“haarcascade_frontalface_alt.xml“);

2. Gesichter oder ähnliches erkennen:

In draw() wird dann die detect() – Funktion aufgerufen, um Gesichter (o. ä.) im Bild zu erkennen.

faceRect = opencv.detect(true);

3. Rechteck um Gesichter zeichnen:

Mit einer weiteren Zeile kann man sich erkannte Gesichter markieren lassen. Dafür muss allerdings vorher noch eine Library (jawa.awt) importieren und ein Rectangle – Array erstellen:

import java.awt.*;
Rectangle[] faceRect;

und in draw():

opencv.drawRectDetect(true);

Position und Größe auslesen:

Will man noch die Position und die Größe der erkannten Objekte auslesen, dann kann man das wie folgt aus dem Rectangle Array machen.

faceRect[index].x …x-Position
faceRect[index].y …y-Position
faceRect[index].width …Breite
faceRect[index].height …Höhe

Änderung der Indizes verhindern:

Wenn man sich in einer Szene mit mehreren Gesichtern die Indizes anzeigen lässt, fällt auf, dass diese nicht konstant dem gleichen Gesicht zugeordnet werden, sondern sie in einer Szene oft mehrmals wechseln und somit eine Zuordnung zu einem Gesicht nicht möglich ist. Daniel Shiffman hat hierzu einen Artikel auf seinem Weblog veröffentlicht, den ich für unsere Library passend adptiert habe.

Eine einfache Funktion, um Schnitte und Kameraschwenks zu erkennen, und dann alle gespeicherten Gesichter zu löschen, wurde von mir ergänzt.

Beispiel: Gesichtserkennung mit Webcam

Image


// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3 und
// Which Face Is Which
// Daniel Shiffman
// April 25, 2011
// http://www.shiffman.net

/**
* KEYS
* Space                   : remember Frame
* s                       : save png
*/

import processing.video.*;
import monclubelec.javacvPro.*;
import java.awt.*; // pour classes Point , Rectangle..
import java.util.*;
// Für WebCam:
Capture cam1;
//GSMovie cam1;

OpenCV opencv; // deklariert ein OpenCV Objekt
Rectangle[] faceRect;

// A list of my Face objects
ArrayList<Face> faceList;

// how many have I found over all time
int faceCount = 0;

float  sum=0;
int scl=1;
int frame=0;
boolean delFaces;

void setup() {

// Für WebCam:
cam1= new Capture(this, 1280, 720);
//cam1 = new GSMovie(this, "maus.mov");

// für WebCam auskommentieren:
//cam1.play();
cam1.start();

// initialisiert OpenCV ---
opencv = new OpenCV(this);

//Vorsicht: bei der Arbeit mit einer Datei muss die Größe genau passen!!!
//opencv.allocate(640, 360);
opencv.allocate(cam1.width, cam1.height);

// Für WebCam:
// opencv.allocate(cam1.getSourceWidth(), cam1.getSourceHeight()); // initialisiert die Buffer von OpenCV
size (opencv.width(), opencv.height());

// Laden Beschreibungsdatei
opencv.cascade("/usr/share/opencv/haarcascades/", "haarcascade_frontalface_alt_tree.xml");

// Liste mit den Gesichtsobjekten
faceList = new ArrayList<Face>();
}

void draw() {

// Dateien und die WebCam brauchen etwas Zeit zum Laden
if (cam1.available()) {

// Einzelne Frames werden gelesen
cam1.read();
opencv.copy(cam1);

// Schnitt oder Kameraschwenk erkennen
println(abs(opencv.sum()-sum)/1000000);
if (abs(opencv.sum()-sum)/1000000 >7) delFaces=true;
else delFaces=false;
sum=opencv.sum();

// Erkennen
faceRect = opencv.detect(false);

image(opencv.getBuffer(), 0, 0);

//Rechteck zeichnen
//opencv.drawRectDetect(true);

// Code ab hier von Daniel Shiffman
// SCENARIO 1: faceList is empty
if (faceList.isEmpty()) {
// Just make a Face object for every face Rectangle
for (int i = 0; i < faceRect.length; i++) {
faceList.add(new Face(faceRect[i].x, faceRect[i].y, faceRect[i].width, faceRect[i].height));
}
// SCENARIO 2: We have fewer Face objects than face Rectangles found from OPENCV
}
else if (faceList.size() <= faceRect.length) {
boolean[] used = new boolean[faceRect.length];
// Match existing Face objects with a Rectangle
for (Face f : faceList) {
// Find faces[index] that is closest to face f
// set used[index] to true so that it can't be used twice
float record = 50000;
int index = -1;
for (int i = 0; i < faceRect.length; i++) {
float d = dist(faceRect[i].x, faceRect[i].y, f.r.x, f.r.y);
if (d < record && !used[i]) {
record = d;
index = i;
}
}
// Update Face object location
used[index] = true;
f.update(faceRect[index]);
}
// Add any unused faces
for (int i = 0; i < faceRect.length; i++) {
if (!used[i]) {
faceList.add(new Face(faceRect[i].x, faceRect[i].y, faceRect[i].width, faceRect[i].height));
}
}
// SCENARIO 3: We have more Face objects than face Rectangles found
}
else {
// All Face objects start out as available
for (Face f : faceList) {
f.available = true;
}
// Match Rectangle with a Face object
for (int i = 0; i < faceRect.length; i++) {
// Find face object closest to faces[i] Rectangle
// set available to false
float record = 50000;
int index = -1;
for (int j = 0; j < faceList.size(); j++) {
Face f = faceList.get(j);
float d = dist(faceRect[i].x, faceRect[i].y, f.r.x, f.r.y);
if (d < record && f.available) {
record = d;
index = j;
}
}
// Update Face object location
Face f = faceList.get(index);
f.available = false;
f.update(faceRect[i]);
}
// Start to kill any left over Face objects
for (Face f : faceList) {
if (f.available) {
f.countDown();
if (f.dead()) {
f.delete = true;
}
}
}
}

// Delete any that should be deleted
for (int i = faceList.size()-1; i >= 0; i--) {
Face f = faceList.get(i);
// Bei einem Schnitt werden alle Gesichter sofort gelöscht
if (f.delete || delFaces) {
faceList.remove(i);
}
}

// Draw all the faces
for (int i = 0; i < faceRect.length; i++) {
noFill();
stroke(255, 0, 0);
rect(faceRect[i].x*scl, faceRect[i].y*scl, faceRect[i].width*scl, faceRect[i].height*scl);
}

for (Face f : faceList) {
f.display();
}
}
}

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

// for Movie
//frame+=500;
//cam1.jump(frame);
//cam1.play();
}

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

// Which Face Is Which
// Daniel Shiffman
// April 25, 2011
// http://www.shiffman.net

class Face {

// A Rectangle
Rectangle r;

// Am I available to be matched?
boolean available;

// Should I be deleted?
boolean delete;

// How long should I live if I have disappeared?
int timer = 63;

// Assign a number to each face
int id;

// Make me
Face(int x, int y, int w, int h) {
r = new Rectangle(x, y, w, h);
available = true;
delete = false;
id = faceCount;
faceCount++;
}

// Show me
void display() {
fill(0, 0, 255, timer*2);
stroke(0, 0, 255);
rect(r.x*scl, r.y*scl, r.width*scl, r.height*scl);
fill(255, timer*2);
text(""+id, r.x*scl+10, r.y*scl+30);
}

// Give me a new location / size
// Oooh, it would be nice to lerp here!
void update(Rectangle newR) {
r = (Rectangle) newR.clone();
}

// Count me down, I am gone
void countDown() {
timer--;
}

// I am deed, delete me
boolean dead() {
if (timer < 0) return true;
return false;
}
}

OpenCV mit Processing – Basics_2


Processing 2.0

Merkmale isolieren – Konturen finden

Die Funktion threshold() dient der Isolierung der für die Mustererkennung relevanten Konturen. Sie blendet Pixel aus, deren Helligkeitswert einen bestimmten Schwellenwert unterschreitet, oder übersteigt.

Beispiel: Threshold

ImageImageImageImageImageImage

// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* changing strokeweight and strokecaps on diagonals in a grid
*
* MOUSE
* left right Threshold 0-1
*
* KEYS

* n                   : show original Image

* 1                   : apply threshold "BINARY"
* 2                   : apply threshold "BINARY_INV"
* 3                   : apply threshold "TRUNK"
* 4                   : apply threshold "TOCERO"
* 5                   : apply threshold "TOCERO_INV"

* s                   : save png
*/

import monclubelec.javacvPro.*;
import java.util.*;

PImage img;

String url="http://kobe.bplaced.net/processing/016_16.JPG";
OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Lädt die Bilddatei
img=loadImage(url, "jpg");

// initialisiert OpenCV ---
opencv = new OpenCV(this);
opencv.allocate(img.width, img.height); // initialisiert die Buffer von OpenCV

opencv.copy(img); // lädt die PImage Datei in den OpenCV Buffer

size (opencv.width(), opencv.height());

// gibt das Bild aus
image(opencv.getBuffer(), 0, 0);

noLoop();
}

void draw() {

noLoop();
}

void keyReleased() {

// Zum speichern des Bildes
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");

// Originalbild anzeigen
if (key == 'n') {
opencv.copy(img);
image(opencv.getBuffer(), 0, 0);
loop();
}

// Threshold Binary
if (key == '1') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.threshold(map(mouseX,0,width,0,1), "BINARY"); // applique seuillage binaire avec seuil 0.5 sur le buffer princi
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Rot-Kanals
loop();
}

// Threshold Binary Invers
if (key == '2') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.threshold(map(mouseX,0,width,0,1), "BINARY_INV");
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Grün-Kanals
loop();
}

// Threshold Trunk
if (key == '3') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.threshold(map(mouseX,0,width,0,1), "TRUNK");
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Blau-Kanals
loop();
}

// Threshold Tozero
if (key == '4') {
opencv.threshold(map(mouseX,0,width,0,1), "TOZERO");
image(opencv.getBuffer(), 0, 0);
loop();
}

// Threshold Tozero Invers
if (key == '5') {
opencv.copy(img);
opencv.threshold(map(mouseX,0,width,0,1), "TOZERO_INV");
image(opencv.getBuffer(), 0, 0);
loop();
}
}

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

Beispiel: Konturen finden

Um Merkmale analysieren zu können, ist es vorher notwendig die markanten Unterschiede zu finden. Geht es um Form, ist es wichtig die Konturen von Objekten zu finden. Für diese Aufgaben kommen die vier hier im Beispiel vorgestellten Filter in Frage.

  1. canny(), canny(Grenzwert1, Grenzwert2), canny(Grenzwert1, Grenzwert2, Faltungskern)
    Die Werte für den Grenzwert1 sind meist zwischen 100 und 200 optimal, Grenzwert2 = 2*Grenzwert1.
    Der Standard für den Faltungskern ist 3, das bedeutet es werden 3*3 Pixel analysiert.
  2. scobel(), scobel(Faltungskern, Maßstab)
    Faltungskern – siehe oben, Maßstab wird mit dem Faltungskern multipliziert.
  3. scharr(),  scharr(Maßstab)
  4. scobel2(), scobel2(Koeffizient), scobel2(Faltungskern, Maßstab, Koeffizient)

ImageImageImageImageImage


// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* changing strokeweight and strokecaps on diagonals in a grid
*
* MOUSE
*
*
* KEYS
* 1                   : show canny()
* 2                   : show Scobel()
* 3                   : show ScHARR
* 4                   : show scobel2
* n                   : show original Image

* s                   : save png
*/

import monclubelec.javacvPro.*;
import java.util.*;

PImage img;

String url="http://kobe.bplaced.net/processing/016_16.JPG";
OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Lädt die Bilddatei
img=loadImage(url, "jpg");

// initialisiert OpenCV ---
opencv = new OpenCV(this);
opencv.allocate(img.width, img.height); // initialisiert die Buffer von OpenCV

opencv.copy(img); // lädt die PImage Datei in den OpenCV Buffer

size (opencv.width(), opencv.height());

// gibt das Bild aus
image(opencv.getBuffer(), 0, 0);
noLoop();
}

void draw() {

noLoop();
}

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

if (key == '1') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.canny(100,200,3); // Filter canny()
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Rot-Kanals
loop();
}

if (key == '2') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.sobel(3,0.9); // Filter Scobel
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Grün-Kanals
loop();
}

if (key == '3') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.scharr(0.4); // Filter ScHARR
image(opencv.getBuffer(), 0, 0);// zeigt das Bild des Blau-Kanals
loop();
}

if (key == '4') {
opencv.copy(img);
opencv.sobel2(3,4,1);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == 'n') {
opencv.copy(img);
image(opencv.getBuffer(), 0, 0);
loop();
}
}

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

Veränderungen zwischen Frames erkennen

… kann man mit der Funktion:

  • sum()         …gibt die Summe der Helligkeitswerte aller Pixel zurück
  • sumRGB() …gibt ein Array mit der Summe der Helligkeitswerte der einzelnen Farbkanäle zurück
  • sumR()     ……gibt die Summe der Helligkeitswerte des R-Kanals zurück
  • sumG()     ……gibt die Summe der Helligkeitswerte des G-Kanals zurück
  • sumB()     ……gibt die Summe der Helligkeitswerte des B-Kanals zurück

Dies ist praktisch, da sich in Videos daraus leicht die ablesen lässt, wie stark sich einzele Frames voneineander unterscheiden.

Obiges Beispiel kann mit folgender Zeile ergänzt werden, um die sum()-Werte auszulesen:

(in draw(), case: ’n‘)

println(opencv.sum());

Mit sum() kann man das Maß der Veränderung zwischen zwei Frames in Zahlen auslesen. Oft braucht man diese Veränderungen auch als Bild. Dafür stellt OpenCV die Funtktion absDiff() zur Verfügung. Sie liest den aktuellen Buffer ein, vergleicht ihn mit dem Inhalt von Memory und speichert die Differenz als Bild-Buffer in Memory2 ab. Zusammen mit threshold(BINARY) kann man damit einen einfärbigen Hintergrund gut von einem Objekt isolieren.

Beispiel: Veränderung vom Frames zeichnen mit absDiff()

Image


// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* KEYS
* Space                   : remember Frame
* s                       : save png
*/
import processing.video.*;
import monclubelec.javacvPro.*;
import java.util.*;

// Für WebCam:
Capture cam1;
//GSMovie cam1;

OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Für WebCam:
cam1= new Capture(this, 640, 360);
//cam1 = new GSMovie(this, "em.mpg");

// für WebCam:
cam1.start();
//cam1.play();

// initialisiert OpenCV ---
opencv = new OpenCV(this);

//Vorsicht: bei der Arbeit mit einer Datei muss die Größe genau passen!!!
opencv.allocate(640, 360);

// Für WebCam:
// opencv.allocate(cam1.width, cam1.height); // initialisiert die Buffer von OpenCV
size (opencv.width(), opencv.height());
frameRate(60);
}

void draw() {

// Dateien und die WebCam brauchen etwas Zeit zum Laden
if (cam1.available()) {

// Einzelne Frames werden gelesen
cam1.read();
opencv.copy(cam1);
image(opencv.getBuffer(), 0, 0);
opencv.absDiff();
image(opencv.getMemory2(), 0, 0);
}
}

void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
}
void keyPressed() {
if (key==' ') {
opencv.remember();  // Schreibt den aktuellen Buffer in Memory
}
}

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

Eine noch einfachere und nicht minder effiziente Art der Hintergrund- Subtraktion bieten der bgsMOG und bgsMOG2 Algotithmus.

Beispiel: Veränderung vom Frames zeichnen mit bgsMOG und bgsMOG2

Image



// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* KEYS
* Space                   : remember Frame
* s                       : save png
*/
import processing.video.*;
import monclubelec.javacvPro.*;
import java.util.*;

// Für WebCam:
Capture cam1;
//Movie cam1;

OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Für WebCam:
cam1= new Capture(this, 640, 360);
//cam1 = new Movie(this, "em.mpg");

// für WebCam:
cam1.start();
//cam1.play();

// initialisiert OpenCV ---
opencv = new OpenCV(this);

//Vorsicht: bei der Arbeit mit einer Datei muss die Größe genau passen!!!
opencv.allocate(640, 360);
opencv.bgsMOGInit(20, 5, 0.5, 10);
//opencv.bgsMOG2Init(1000, 16, false);

// Für WebCam:
//opencv.allocate(cam1.width, cam1.height); // initialisiert die Buffer von OpenCV
size (opencv.width(), opencv.height());
frameRate(60);
}

void draw() {

// Dateien und die WebCam brauchen etwas Zeit zum Laden
if (cam1.available()) {

// Einzelne Frames werden gelesen
cam1.read();
opencv.copy(cam1);
image(opencv.getBuffer(), 0, 0);
opencv.bgsMOGApply(opencv.Buffer, opencv.BufferGray, 0);
//opencv.bgsMOG2Apply(opencv.Buffer, opencv.BufferGray, -1);
image(opencv.getBufferGray(), 0, 0);
}
}

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

Ziegenproblem


Das Spiel besteht darin, eine Tür zu erraten, hinter dem ein Geldpreis versteckt ist. Der Preis wird zufällig hinter einer von drei Türen versteckt. Dann darf der Spieler auf ein Feld setzen. Darauf wird ein leeres Feld geöffnet, aber niemals das bereits gewählte. Jetzt hat der Spieler die Wahl, entweder bei seiner ersten Entscheidung zu bleiben, oder auf das andere Feld zu wechseln.

Siehe Wikipedia Ziegenproblem.

Beispiel: Ziegenproblem starte Applet

Image


// 3 Programm Modes
// 1 .. play mode (vote yourself
// 2 .. simulation mode (calculates percentage)

//GUI Library ControlP5
import controlP5.*;
ControlP5 controlP5;
CheckBox checkbox;

PImage car, goat;

int mode = 0;
public int Iterations=10;
int trys, hits;
boolean doorIsOpen =false;
boolean showCar  = false;
boolean youWon=false;
boolean simulate= false;
boolean changeChoice =false;

// Erstelle Array mit dem Türen
Door[] door = new Door[3];

void setup() {
size(600, 400);
smooth();

reset();
trys=0;
rectMode(CENTER);
imageMode(CENTER);
textAlign(CENTER, CENTER);

car=loadImage("car.png");
goat=loadImage("goat.png");

fill(0);
stroke(255);
strokeWeight(4);

// Steuerelemente erstellen
controlP5 = new ControlP5(this);
controlP5.begin(10, 10);
controlP5.addButton("Play_Mode");
controlP5.addButton("Simulation_Mode").linebreak();
checkbox = controlP5.addCheckBox("checkBox", 180, 10);
checkbox.setColorForeground(color(120));
checkbox.setColorActive(color(255));
checkbox.setColorLabel(color(128));
checkbox.setItemsPerRow(1);
checkbox.setSpacingColumn(30);
checkbox.setSpacingRow(10);
checkbox.addItem("Change Choice", 0);
controlP5.addSlider("Iterations", 0, 10000, 10, 280, 10, 200, 10);
controlP5.addButton("Reset");
controlP5.addButton("OpenDoor");
controlP5.addButton("ShowCar");
controlP5.end();
}

void draw() {

//Modes wechseln
switch(mode) {
case 0:
drawScreenPlay();
break;
case 1:
drawScreenSimulation();
break;
default:
background(0);
break;
}
}

// Wenn aktiv gespielt wird
void drawScreenPlay() {
drawBackground();
}

// Simulation
void drawScreenSimulation() {
for (int j = 0; j<Iterations; j++) {
if (simulate) {
int selection = (int)random(0, 3);
door[selection].isChosen=true;
OpenDoor();
if (changeChoice) {
for (int i=0;i<3;i++) {
if (door[i].isChosen==false && door[i].isOpened==false) {
door[i].isChosen=true;
}
}
door[selection].isChosen=false;
}

ShowCar();

reset();
drawBackground();
}
}
simulate=false;
}

void drawBackground() {
background(50);
fill(255);
textSize(50);
text("Ziegenproblem", width/2, 100);
textSize(12);
text("Versuche: "+trys+" Treffer: "+hits+" Quote: "+(float)hits/trys*100+"%", width/2, 380);
if (youWon) {
textSize(40);
fill(200, 50, 50);
text("You Won!!!", width/2, 350);
}
for (int i=0;i<3;i++) {
door[i].render();
}
}

void mousePressed() {
if (mouseY > 100 &! showCar) {
for (int i=0;i<3;i++) {
door[i].isHit();
}
}
}

// Aktion Button Play Mode
public void Play_Mode() {
loop();
trys=0;
hits=0;
mode=0;
}

// Aktion Slider Iterations
public void Iterations(int wh) {
Iterations= int(wh);
}

// Aktion Button Simulation Mode
public void Simulation_Mode() {
simulate=true;
trys=0;
hits=0;
mode=1;
}

// Aktion Button Reset
public void Reset() {
reset();
}

// Aktion Radio Button
void controlEvent(ControlEvent theEvent) {
if (theEvent.isGroup()) {
changeChoice=!changeChoice;
}
}

// Aktion Button OpenDoor
public void OpenDoor() {
while (!doorIsOpen) {
int doorToOpen=int(random(0, 3));
if (!door[doorToOpen].isChosen && !door[doorToOpen].hasCar) {
door[doorToOpen].isOpened=true;
//println("DoorOpened: "+ doorToOpen);
doorIsOpen=true;
}
}
}

public void reset() {
doorIsOpen =false;
// create Doors
for (int i=0;i<3;i++) {
door[i] = new Door(i);
}

// verstecke Auto
int hideCarIn=(int) random(0, 3);
door[hideCarIn].hasCar=true;
boolean showCar  = false;
youWon=false;
}

// Aktion Button ShowCar
public void ShowCar() {

if (doorIsOpen) showCar  = true;
int hasCar=5;
int isChosen=5;
for (int i=0;i<3;i++) {
if (door[i].hasCar) hasCar=i;
if (door[i].isChosen) isChosen=i;
}
trys++;
if (hasCar==isChosen) {
hits++;
youWon=true;
}
}

class Door {

int index;
boolean isChosen, hasCar, isOpened;

Door (int index) {
this.index = index;
hasCar=false;
showCar=false;
isChosen=false;
}

void render() {
pushMatrix();
pushStyle();
stroke(255);
fill(0);
translate((index+1)*width/4, 240);

if (isChosen) {
strokeWeight(4);
stroke(0, 200, 50);
} else fill(0);
rect(0, 0, 100, 150);
stroke(255);
fill(0);
textSize(50);
if (hasCar && showCar) {
fill(255, 0, 40, 50);
image(car, 0, 0, 90, 90);
} else if (isOpened) {
fill(255, 0, 40, 50);
image(goat, 0, 0, 90, 90);
}
popStyle();
popMatrix();
}

void isHit() {
if (mouseX>((index+1)*width/4)-50
&& mouseX<((index+1)*width/4)+50
&& mouseY>240-75
&& mouseY<240+75) {
isChosen=true;
}
else  isChosen=false;
}
}

Minim – Audio Analyse


Pegel Spektrum zeichnen

Um ein Pegel Spektrum darzustellen, kann man aus der AudioSource den AudioBuffer mix auslesen. Die Methode toArray() des AudioBuffers mix gibt ein Float Array mit den einzelnen Pegeln im Buffer zurück. Diese Werte kann man dann verwenden, um ein Spektrum zu zeichnen. Ich habe für die Darstellung dann der Einfachheit halber Rechtecke gewählt.

Achtung: Es handelt sich hier um ein Spektrum der Pegel über die Zeit, nicht um ein Frequenzspektrum!

Beispiel: Spektrum zeichnen

Image


// Library importieren
import ddf.minim.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;

int x, y;

// Anzahl der Peaks
int grid=128;

// Abstand zwischen den Peaks
int spacing=1;

// Ausschlagmaximum für Peaks festlegen
float yScale = 2;

void setup() {
size(1024, 400);
smooth();
noStroke();

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
input = minim.loadFile(&quot;http://mp3stream1.apasf.apa.at:8000&quot;);

// Wiedergabe starten
input.play();
}

void draw() {

// für etwas Bewegunsunschärfe
fill(50, 10);
rect(0, 0, width, height);

// Auslesen und speichern des Spektrums
float[] buffer = input.mix.toArray();

// Breite der Rechtecke berechnen
for (int i=1; i <= buffer.length; i+=buffer.length/grid) {
float x = map(i, 0, buffer.length, 0, width);
float y = map(buffer[i-1]*yScale, -1, 1, 0, height) ;
fill (102, 145, 250,100);

// Rechteck zeichnen
rect(x+spacing, height, width/grid-2*spacing, -y);
}
}

void stop()
{
// Player in schließen
input.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Frequenzen darstellen

Der Begriff Spekturm bezieht sich in der Regel auf ein Frequenzspektrum. Dieses kann mit Hilfe der Klasse FFT  und der forward() Methode aus dem AudioBuffer errechnet werden. Dafür werden die Pegel im Buffer einer Fourier-Transformation unterzogen. Als Ergebnis erhält man keine einzelnen Frequenzen, sondern Frequenzbänder.

Umgekehrt kann man auch aus einem Frequenzspektrum ein Zeit-Pegel Spektrum ausgeben. Die Methode dafür heißt inverse();

Der auswertbare Frequenzbereich kann außerdem die halbe Sample-Frequenz nicht übersteigen. Die default Sample-Frequenz bei .mp3 und auf CD ist 44100 Hz, was eine maximale Frequenz von 22050 Hz für die Auwertung ergibt.

Einfaches Frequenzspektrum zeichnen

Um die FFT Klasse nutzen zu können, müssen wir zusätzlich zum letzten Beispiel die minim.analysis – Bibliothek importieren.

import ddf.minim.analysis.*;

Der folgende Code stellt ein Abwandlung des obigen Beispiels dar. Er zeichnet die Frequenzspektren der beiden Kanäle (left und right).

Beispiel: FFT Frequenzspektrum linear

Image

// Library importieren
import ddf.minim.*;
import ddf.minim.analysis.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;
FFT fftR, fftL;

int x, y;

// Anzahl der Peaks
int grid=32;

// Abstand zwischen den Peaks
int spacing=1;

// Ausschlagmaximum für Peaks festlegen
float yScale = 1;

void setup() {
size(1024, 400);
smooth();
noStroke();

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
input = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
input.play();
input.printControls();

// FFT-Instanz für die Spektrumsanalyse der beiden Kanäle
fftR = new FFT (input.bufferSize (), input.sampleRate ());
fftL = new FFT (input.bufferSize (), input.sampleRate ());
}

void draw() {

// für etwas Bewegunsunschärfe
fill(50, 10);
rect(0, 0, width, height);

// forwar FFT Analyse durchführen
fftR.forward(input.right);
fftL.forward(input.left);

// rechter Kanal
fill(255);
text("right Channel", 10, height/2+20);

// Breite der Rechtecke berechnen
for (int i=1; i <= fftR.specSize(); i+=fftR.specSize()/grid) {
float x = map(i, 0, fftR.specSize(), 0, width);
float y = map(fftR.getBand(i)*yScale, 0, 100, 0, height/2) ;
fill (102, 145, 250, 100);

// Rechteck zeichnen
rect(x+spacing, height, width/grid-2*spacing, -y);
}

// linker Kanal
fill(255);
translate(0, -height/2);
text("left Channel", 10, height/2+20);

// Breite der Rechtecke berechnen
for (int i=1; i <= fftL.specSize(); i+=fftL.specSize()/grid) {
float x = map(i, 0, fftL.specSize(), 0, width);
float y = map(fftL.getBand(i)*yScale, 0, 100, 0, height/2) ;
fill (102, 145, 250, 100);

// Rechteck zeichnen
rect(x+spacing, height, width/grid-2*spacing, -y);
}
}

void stop()
{
// Player in schließen
input.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Wie man an dem Spektrum unschwer erkennen kann, entspricht die Darstellung nicht unserer Wahrnehmung. Eine logarithmische Darstellung der Frequenzen ist hier angebracht. Mit FFT Klasse kann man eine solche Umwandlung mit der Methode logAverages() vornehmen.

Beispiel: FFT Frequenzspektrum logarithmisch

Image

// Library importieren
import ddf.minim.*;
import ddf.minim.analysis.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;
FFT fftR, fftL;

int x, y;

// Anzahl der Peaks
int grid=20;

// Abstand zwischen den Peaks
int spacing=5;

// Ausschlagmaximum für Peaks festlegen
float yScale = 1;

void setup() {
  size(1024, 400);
  smooth();
  noStroke();

  // Konstruktor des Minim Objekts aufrufen
  minim = new Minim(this);

  // Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
  input = minim.loadFile("http://mp3stream1.apasf.apa.at:8000";,2048);

  // Wiedergabe starten
  input.play();
  input.printControls();

  // FFT-Instanz für die Spektrumsanalyse der beiden Kanäle
  fftR = new FFT (input.bufferSize (), input.sampleRate ());
  fftL = new FFT (input.bufferSize (), input.sampleRate ());
  fftR.logAverages(11, 16);
  fftL.logAverages(11, 16);
}

void draw() {

  // für etwas Bewegunsunschärfe
  fill(50, 10);
  rect(0, 0, width, height);

  // forwar FFT Analyse durchführen
  fftR.forward(input.right);
  fftL.forward(input.left);

  // rechter Kanal
  fill(255);
  text("right Channel", 10, height/2+20);

  // Breite der Rechtecke berechnen
  for (int i=0; i < fftR.avgSize(); i+=fftR.avgSize()/grid) {
    //println(fftR.avgSize());
    float x = map(i, 0, fftR.avgSize(), 0, width);
    //println(fftR.getAvg(i));
    float y = map(fftR.getAvg(i)*yScale, 0, 100, 0, height/5) ;
    fill (102, 145, 250, 100);

    // Rechteck zeichnen
    rect(x+spacing, height, width/grid-2*spacing, -y);
  }

  // linker Kanal
  fill(255);
  translate(0, -height/2);
  text("left Channel", 10, height/2+20);

  // Breite der Rechtecke berechnen
  for (int i=0; i < fftL.avgSize(); i+=fftL.avgSize()/grid) {
    float x = map(i, 0, fftL.avgSize(), 0, width);
    float y = map(fftL.getAvg(i)*yScale, 0, 100, 0, height/5) ;
    fill (102, 145, 250, 100);

    // Rechteck zeichnen
    rect(x+spacing, height, width/grid-2*spacing, -y);
  }
}

void stop()
{
  // Player in schließen
  input.close();
  // Minim Object stoppen
  minim.stop();

  super.stop();
}

Eine weitere Möglichkeit der FFT Klasse ist die Veränderung der Pegel einzelner Frequenzbereiche mit scaleBand() und setBand(). Siehe dazu die JavaDoc.

Beat Detection

Auch hier gibt es wieder 2 Möglichkeiten. Einerseits kann man nur mit den Levels (Amplituden) arbeiten - SOUND_ENERGY. Oder aber man nutzt FREQUENCY_ENERGY und greift die Levels in einzelnen Frequenzbändern ab.

Beispiel: BeatDetect mit SoundEnergy

Image

// Library importieren
import ddf.minim.*;
import ddf.minim.analysis.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;
BeatDetect beat;

float eRadius;

void setup() {
size(512, 512);
smooth();
noStroke();

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
input = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
input.play();
input.printControls();

// Erstellt die BeatDetect Instanz
beat = new BeatDetect();

ellipseMode(CENTER_RADIUS);
}

void draw() {

// für etwas Bewegunsunschärfe
fill(50, 50);
rect(0, 0, width, height);

// Initiiert die BeatDetection
beat.detect(input.mix);

fill (102, 145, 250, 100);

// Trigger der BeatDetection
if ( beat.isOnset() ) eRadius = 3;

if ( eRadius < 0.1 ) eRadius = 0.1;

// Zeichnet die Kreise
for (float i=1;i<10;i+=0.3) {
fill(102, 145, 250, 10/i*i);
ellipse(width/2, height/2, eRadius*i*i, eRadius*i*i);
}
eRadius *= 0.95;
}

void stop()
{
// Player in schließen
input.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Beispiel: BeatDetect mit FrequencyEnergy

Hier kann man mit den Methoden isKick(), isSnare() und isHat() Peaks in den Frequenzbändern abrufen, die den jeweiligen Schlagzeugbausteinen entsprechen. Das funktioniert allerdings nicht bei allen Musiktypen gleich gut. Bei Problemen kann man noch auf die Funktion isRange(int low, int high, int threshold) zurückgreifen.

Image

// Library importieren
import ddf.minim.*;
import ddf.minim.analysis.*;

// Objekte erstellen
Minim minim;
AudioPlayer input;
BeatDetect beat;
BeatListener bl;

float radKick, radSnare, radHat;

void setup() {
size(1024, 512);
smooth();
noStroke();

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
input = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
input.play();
input.printControls();

// Erstellt die BeatDetect Instanz
// Im Frequency Mode müssen BufferSize und SampleRate übergeben werden.
beat = new BeatDetect(input.bufferSize(), input.sampleRate());

//Setzt die Zeit, in der der Algorithmus keine weiteren Beats meldet
beat.setSensitivity(200);
ellipseMode(CENTER_RADIUS);
textAlign(CENTER, CENTER);
}

void draw() {

// für etwas Bewegunsunschärfe
fill(50, 100);
rect(0, 0, width, height);

// Initiiert die BeatDetection
beat.detect(input.mix);

fill (102, 145, 250, 100);

// Trigger der BeatDetection
if ( beat.isKick() ) radKick = 2.5;
if ( beat.isSnare() ) radSnare = 2.5;
if ( beat.isHat() ) radHat = 2.5;

if ( radKick < 0.1 ) radKick = 0.1;
if ( radSnare < 0.1 ) radSnare = 0.1;
if ( radHat < 0.1 ) radHat = 0.1;

// Zeichnet die Kreise
for (float i=1;i<10;i+=0.3) {
fill(102, 145, 250, 10/i*i);
ellipse(width/4, height/2, radKick*i*i, radKick*i*i);
}

// Zeichnet die Kreise
for (float i=1;i<10;i+=0.3) {
fill(255, 0, 50, 10/i*i);
ellipse(width/2, height/2, radSnare*i*i, radSnare*i*i);
}

// Zeichnet die Kreise
for (float i=1;i<10;i+=0.3) {
fill(255, 200, 0, 10/i*i);
ellipse(width*3/4, height/2, radHat*i*i, radHat*i*i);
}

//Zeichnet den Text
fill(255);
text("Kick", width/4, height/2);
text("Snare", width/2, height/2);
text("Hat", width*3/4, height/2);

radKick *= 0.9;
radSnare *= 0.9;
radHat *= 0.9;
}

void stop()
{
// Player in schließen
input.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Aufgaben zu den Basics


  1. Erkläre die Bedeutung folgender Symbole und Programmanweisungen:
    boolean    for    >=    %    int   <=    &&    char
    | |    ‚     {    ++    String    }    +=    „“    = –     ;    ==
    —    if    !=    -=    //    else    >    *    /* */    while    <    /
  2. Wieviel bit Speicher reserviert eine Variable vom Typ boolean?
    a) 32    c) 16
    b) 8      d) 1
  3. Was ist das Ergebnis des folgenden Programms?
    int a=0;
    for (int i=0; i<5; i++){
        a++;
        a += i+1;
    }a) 10    c) 20
    b) 15    d) 0
  4. Nimm 2 Variablen, x und y. Wir wollen nun die Ausganswerte vertauschen, also von x nach y und von y nach x übertragen. Welches der folgenden Programme macht das?
    1. x = y;
      y = x;
    2. tempX = x;
       tempY = y;
       x = tempX;
       y = tempY;
    3. y = x;
      tempX = x;
      x = tempX;
    4. tempX = x;
      x = y;
      y = tempX;
  5. Schreibe Programme, die folgende Ausgaben produzieren:
    Tipp: Verwende Schleifen und Modulo(%)
    Image    Image    Image   Image    Image
  6. Schreibe Programme, die folgende Ausgaben produzieren:
    Tipp: Verwende sin() und cos()!
    Image    Image    ImageImage
  7. Schreibe Programme, die folgende Ausgaben produzieren:
    Tipp: Verwende random() und noise()!
    Image    Image    Image     Image    
  8. Schreibe Programme, die folgende Ausgaben produzieren:
    Image    Image
  9. Schreibe den kürzest möglichen Code, der die folgenden Zahlenreihen ausgibt. Verwende dafür nur +, -, *, 0,
    und %.
    012340123401234
    000010000100001
    111101111011110
  10. Schreibe ein Programm, das den Mauszeiger auf einem 10 Punkte-Raster einschnappen lässt.
    Tipp: Verwende dabei die round() Funktion.

Minim Sound abspielen und aufnehmen


Minim ist die Standard Audio-Bibliothek in Processing.

Sie bietet die Möglichkeit Sounds abzuspielen, aufzunehmen, zu verändern, zu synthetisieren und zu analysieren.

Minim

Um mit der Library arbeiten zu können, müssen wir zuerst ein Objekt der Minim-Klasse erstellen. Dieses Objekt kann dann zur Sound Ein- und Ausgabe benutzt werden. Dabei ist zu beachten, dass vor dem Schließen des Programms erst jegliche Ein- und Ausgaben geschlossen, und dann das Minim-Objekt gestoppt werden muss.

// Import der Bibliothek
import ddf.minim.*;

// erstellen eines Objektes (einer Instanz)
Minim minim;

// Aufruf des Konstruktors
minim = new Minim(this);

// Sound vom Eingang der Soundkarte
input = minim.getLineIn();

// Zuerst Eingabe schließen und dann Minim stoppen
input.close();
minim.stop();

Sound abspielen

Um Sound mit Minim abspielen zu können, muss zuerst eine Quelle definiert werden. Dies kann eine Datei (im /data-Ordner des Sketches), oder ein Eingang der Soundkarte sein. Mögliche Dateitypen sind: WAV, AIFF, AU, SND, and MP3.

Interessant ist vielleicht, dass die Dateien zurückgespult werden müssen. D.h. wenn sie an ihr Ende kommen, ist es so, wie bei einem analogen Plattenspieler. Der dreht sich noch, auch wenn keine Musik gespielt wird.

Für die Soundwiedergabe gibt es grundsätzlich 3 Möglichkeiten:

AudioSample

  • Datei wird vor dem Abspielen in den Speicher geladen.
  • Für sehr kurze Sequenzen geeignet.
  • Kann nur getriggert, nicht gelooped, oder  sonst etwas werden.
  • AudioSample sample = loadSample(„mySample.mp3“);

Beispiel: Trigger

Audio Snippet

  • keine Echtzeit Effekte
  • kein Zugriff auf Samples
  • Datei wird vor dem Abspielen in den Speicher geladen.
  • AudioSnippet snippet = loadSnippet(„mySnippet.mp3“);

AudioPlayer

  • zum Apspielen längerer Audio Dateien.
  • Datei wird „on the fly“ decodiert. Das spart Speicher, erhöht aber die Latenzzeit.
  • AudioPlayer player = loadFile(„myFile.mp3“);

Beispiel: Player

Beispiel: Abspielen des FM4 Streams

// Library importieren
import ddf.minim.*;

// Objekte erstellen
Minim minim;
AudioPlayer in;

void setup()
{
size(512, 200);

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
in = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
in.play();
}

void draw()
{

}

void stop()
{
// Player in schließen
in.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

Methoden des AudioPlayer:

  • cue(int millis): legt die Abspielposition fest (in ms vom Start).
  • getMetaData(): gibt die ID3 Tags als String zurück.
    • album(), author(), comment(), composer(), copyright(), date(), disc(), encoded(), fileName(), genre(), length(), orchestra(), publisher(), title(), track().
  • isLooping(): gibt true zurück, wenn das Stück noch mehr als einmal gespielt werden muss.
  • isPlaying(): bei Wiedergabe true
  • length(): gibt die länge des Stücks in Millisekunden als int zurück.
  • loop(): schaltet looping ein.
  • loop(int n): schaltet looping ein. Spielt das Stück n mal ab.
  • pause(): pausiert die Wiedergabe
  • play(): startet die Wiedergabe
  • play(int millis): startet die Wiedergabe bei millis Millisekunden.
  • position(): gibt die aktuelle Position im Stück zurück
  • rewind(): spult an den Beginn.
  • setLoopPoints(int start, int end): setzt loopPoints ()
  • skipp(int millis): Spult millis Millisekunden

Außerdem bietet der AudioPlayer noch einige vererbte Methoden.

  • printControls(): gibt die Einstellungsmöglichkeiten des Eingangsgerätes auf der Konsole aus.
    Die zurückgegebenen Einstellungen sind hier beschrieben.
  • in.setVolume(int volume): legt die Lautstärke fest. Der Bereich ist von der jeweiligen Soundkarte abhängig (bei mir 0-65536).
  • in.getVolume(): gibt die aktuelle Lautstärkeneinstellung zurück.
  • mix.level(), left.level(), right.level(): gibt den Pegel des jeweiligen Kanals als float zurück

Aufgabe: Verändere das obige Programm so, dass man die Lautstärke verändern kann und der Pegel auf einfache Art und weise visualisiert wird.

Sound aufnehmen

Mit der AudioRecorder Klasse kann man den Inet-Radio-Sound auch aufnehmen. Dabei wird der Dateiname des aufgenommenen Stücks aus den Metadaten des Streams generiert. Bei FM4 ist das leider nur „FM4“. Bei anderen Sendern kann man vielleicht aber auch den Titel und den Interpreten auslesen.

// Library importieren
import ddf.minim.*;

// Objekte erstellen
Minim minim;
AudioPlayer in;
AudioRecorder recorder;

int x, y;

void setup() {
size(512, 200);
y=height/2;

// Konstruktor des Minim Objekts aufrufen
minim = new Minim(this);

// Livestream vom FM4 laden, Größe des default sample buffer's ist 1024
in = minim.loadFile("http://mp3stream1.apasf.apa.at:8000");

// Wiedergabe starten
in.play();

// Recorder erstellen
// Dateiname für Aufnahme festlegen
recorder = minim.createRecorder(in, in.getMetaData().title()+"_"+timestamp()+"_##.wav", true);

noFill();
background(0);
stroke(255);
}

void draw() {
// wenn gerade aufgenommen wird, wird rot gezeichnet, sonst weiß!
if (recorder.isRecording()) stroke(255, 0, 0);
else stroke(255);

// kleine Anmation
fill(0, 5);
rect(0, 0, width, height);
noFill();
// Kreisgröße Abhängig von Lautstärke
float dim = in.mix.level () * width;
// Kreis x-Position verschieben
x += in.mix.level() * 20;
// Kreis zeichnen
ellipse (x, y, dim, dim);
if (x > width) {
x = 0;
}
}

void keyReleased()
{
// mit "r" wird die Aufnahme gestartet
if ( key == 'r' ) {
if (recorder.isRecording()) recorder.endRecord();
else {
// Dateiname für Aufnahme festlegen
recorder = minim.createRecorder(in, in.getMetaData().title()+"_"+timestamp()+"_##.wav", true);
recorder.beginRecord();
}
}

// erst mit "s" wird die Aufnahme gespeichert
if ( key == 's' ) {
recorder.save();
println("Done saving.");
}
}

void stop()
{
// Player in schließen
in.close();
// Minim Object stoppen
minim.stop();

super.stop();
}

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


OpenCV mit Processing – Basics_1


Processing 2.0

Um mit OpenCV arbeiten zu können, muss man erst die Library impotieren, dann ein OpenCV Objekt deklarieren und intitialisieren.

import monclubelec. javacvPro.*

OpenCV opencv;
in setup():
opencv = new OpenCV(this);

OpenCV arbeitet mit Puffern. Beim Aufruf der allocate() Funktion werden die unten im Bild dargestellten Puffer erstellt.

Image

Diese Buffer sind dann auf unterschiedliche Arten ansprechbar. Einfachste Art: Man zeigt das OpenCV Bildobjekt im Programmfenster:

opencv.copy(img); // kopiert PImage in den OpenCV Buffer
image(opencv.image(), 0, 0); //zeichnet das OpenCV Bildobjekt links oben

Will man auf das Graustufen-Bild zugreifen:
opencv.copyToGray(img);
image(opencv.getBufferGray(), 0, 0);

Ausgabe der Kanalbilder RGB:
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
//die Umkehroperation wäre hier mergerRGB()
image(opencv.getBufferR(),0, 0);// zeigt das Bild des Rot-Kanals

Es besteht auch die Möglichkeit, diese Buffer separat für verschiedene Bildgrößen zu erstellen. Z. B. um Objekte zu vergleichen.

opencv.allocateBuffer( width, height);
opencv.allocateMemory( width, height);
opencv.allocateMemory2( width, height);

Mit diversen remember()-, copyTo()-, remember2()-, restore()- und getMemory()- Funktionen kann man diese Buffer nutzen, um Bilder zwischen zu speichern.

Hier ein Überblick von: http://www.mon-club-elec.fr/pmwiki_reference_lib_javacvPro/pmwiki.php?n=Main.Presentation.

Image

Beispiel: Einfache Bildmanipulation unter Verwendung der OpenCV Buffer und Filter

ImageImageImageImageImageImageImageImageImageImageImageImageImageImageImage


// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* changing strokeweight and strokecaps on diagonals in a grid
*
* MOUSE
*
*
* KEYS
* r                   : show Red Channel Image
* g                   : show Green Channel Image
* b                   : show Blue Channel Image
* w                   : show Gray Image
* n                   : show original Image
* h                   : flip Image horizontaly
* v                   : flip Image verticaly
* i                   : invert Image

* 1                   : apply "blur" Filter
* 2                   : increase Brightness
* 3                   : decrease Brightness
* 4                   : increase Contrast
* 5                   : decrease Contrast
* 6                   : multiply Red
* 7                   : multiply Green
* 8                   : multiply Blue
* 9                   : smooth Image

* s                   : save png
*/

import monclubelec.javacvPro.*;
import java.util.*;

PImage img;

String url="http://kobe.bplaced.net/processing/016_16.JPG";
OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Lädt die Bilddatei
img=loadImage(url, "jpg");

// initialisiert OpenCV ---
opencv = new OpenCV(this);
opencv.allocate(img.width, img.height); // initialisiert die Buffer von OpenCV

opencv.copy(img); // lädt die PImage Datei in den OpenCV Buffer

size (opencv.width(), opencv.height());

// gibt das Bild aus
image(opencv.getBuffer(), 0, 0);

noLoop();
}

void draw() {

noLoop();
}

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

if (key == 'r') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferR(), 0, 0);// zeigt das Bild des Rot-Kanals
loop();
}

if (key == 'g') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferG(), 0, 0);// zeigt das Bild des Grün-Kanals
loop();
}

if (key == 'b') {
opencv.copy(img); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferB(), 0, 0);// zeigt das Bild des Blau-Kanals
loop();
}

if (key == 'w') {
opencv.copyToGray(img);
image(opencv.getBufferGray(), 0, 0);
loop();
}

if (key == 'n') {
opencv.copy(img);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == 'h') {
opencv.flip(opencv.HORIZONTAL);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == 'v') {
opencv.flip(opencv.VERTICAL);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == 'i') {
opencv.invert();
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '1') {
opencv.blur(10);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '2') {
opencv.brightness(+10);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '3') {
opencv.brightness(-10);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '4') {
opencv.contrast(+10);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '5') {
opencv.contrast(-10);
image(opencv.getBuffer(), 0, 0);
loop();
}
if (key == '6') {
opencv.multiply(0.9, 1, 1);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '7') {
opencv.multiply(1, 0.9, 1);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '8') {
opencv.multiply(1, 1, 0.9);
image(opencv.getBuffer(), 0, 0);
loop();
}

if (key == '9') {
opencv.smooth(3);
image(opencv.getBuffer(), 0, 0);
loop();
}
}

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

Beispiel: Alles wie oben, nur mit Video aus Datei

// Verändert von Thomas Koberger
// im Original von:
// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

/**
* changing strokeweight and strokecaps on diagonals in a grid
*
* MOUSE
*
*
* KEYS
* r                   : show Red Channel Image
* g                   : show Green Channel Image
* b                   : show Blue Channel Image
* w                   : show Gray Image
* n                   : show original Image
* h                   : flip Image horizontaly
* v                   : flip Image verticaly
* i                   : invert Image

* 1                   : apply "blur" Filter
* 2                   : increase Brightness
* 3                   : decrease Brightness
* 4                   : increase Contrast
* 5                   : decrease Contrast
* 6                   : multiply Red
* 7                   : multiply Green
* 8                   : multiply Blue
* 9                   : smooth Image

* s                   : save png
*/

import monclubelec.javacvPro.*;
import processing.video.*;
import java.util.*;

// Für WebCam:
Capture cam1;
//Movie cam1;

PImage img, imgSrc;
char mode='n';

OpenCV opencv; // deklariert ein OpenCV Objekt

void setup() {

// Für WebCam:
cam1= new Capture(this, 1280, 720);
//cam1 = new Movie(this, "em.mpg");

// für WebCam auskommentieren:
cam1.start();

// initialisiert OpenCV ---
opencv = new OpenCV(this);

//Vorsicht: bei der Arbeit mit einer Datei muss die Größe genau passen!!!
opencv.allocate(1280, 720);

// Für WebCam:
// opencv.allocate(cam1.getSourceWidth(), cam1.getSourceHeight()); // initialisiert die Buffer von OpenCV

size (opencv.width(), opencv.height());
}

void draw() {

// Dateien und die WebCam brauchen etwas Zeit zum Laden
if (cam1.available()) {

// Einzelne Frames werden gelesen
cam1.read();

switch (mode) {
case 'n':
opencv.copy(cam1);
image(opencv.getBuffer(), 0, 0);
break;

case 'r':
opencv.copy(cam1); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferR(), 0, 0);// zeigt das Bild des Rot-Kanals
break;

case 'g':
opencv.copy(cam1); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferG(), 0, 0);// zeigt das Bild des Grün-Kanals
break;

case 'b':
opencv.copy(cam1); // kopiert PImage in den OpenCV Buffer
opencv.extractRGB(); //extrahiert die Kanäle
image(opencv.getBufferB(), 0, 0);// zeigt das Bild des Blau-Kanals
break;

case 'w':
opencv.copyToGray(cam1);
image(opencv.getBufferGray(), 0, 0);
break;

case 'h':
opencv.copy(cam1);
opencv.flip(opencv.HORIZONTAL);
image(opencv.getBuffer(), 0, 0);
break;

case 'v':
opencv.copy(cam1);
opencv.flip(opencv.VERTICAL);
image(opencv.getBuffer(), 0, 0);
break;

case 'i':
opencv.copy(cam1);
opencv.invert();
image(opencv.getBuffer(), 0, 0);
break;

case '1':
opencv.copy(cam1);
opencv.blur(30);
image(opencv.getBuffer(), 0, 0);
break;

case '2':
opencv.copy(cam1);
opencv.brightness(+30);
image(opencv.getBuffer(), 0, 0);
break;

case '3':
opencv.copy(cam1);
opencv.brightness(-30);
image(opencv.getBuffer(), 0, 0);
break;

case '4':
opencv.copy(cam1);
opencv.contrast(+30);
image(opencv.getBuffer(), 0, 0);
break;

case '5':
opencv.copy(cam1);
opencv.contrast(-30);
image(opencv.getBuffer(), 0, 0);
break;

case '6':
opencv.copy(cam1);
opencv.multiply(0.7, 1, 1);
image(opencv.getBuffer(), 0, 0);
break;

case '7':
opencv.copy(cam1);
opencv.multiply(1, 0.7, 1);
image(opencv.getBuffer(), 0, 0);
break;

case '8':
opencv.copy(cam1);
opencv.multiply(1, 1, 0.7);
image(opencv.getBuffer(), 0, 0);
break;

case '9':
opencv.copy(cam1);
opencv.smooth(3);
image(opencv.getBuffer(), 0, 0);
break;
}
}
}

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

if (key == 'r') {
mode='r';
}

if (key == 'g') {
mode='g';
}

if (key == 'b') {
mode='b';
}

if (key == 'w') {
mode='w';
}

if (key == 'n') {
mode='n';
}

if (key == 'h') {
mode='h';
}

if (key == 'v') {
mode='v';
}

if (key == 'i') {
mode='i';
}

if (key == '1') {
mode='1';
}

if (key == '2') {
mode='2';
}

if (key == '3') {
mode='3';
}

if (key == '4') {
mode='4';
}

if (key == '5') {
mode='5';
}
if (key == '6') {
mode='6';
}

if (key == '7') {
mode='7';
}

if (key == '8') {
mode='8';
}

if (key == '9') {
mode='9';
}
}

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


OpenCV mit Processing Installation auf Ubuntu 12.04


Processing 2.0

1. OpenCV installieren

Da OpenCV in den Paketquellen von Ubuntu enthalten ist, gestaltet sich die Installation denkbar einfach:

sudo apt-get install libopencv-*

Bei mir haben dann noch einige Pakete gefehlt:

sudo apt-get install libgstreamer-plugins-base0.10-dev libgstreamer0.10-dev libglib2.0-dev

2. Die Processing OpenCV Library 

Für Processing gibt es eine OpenCV Library, die sich allerdings nur für OpenCV 1.0 nutzen lässt. Nun hat Xavier Hinault eine neue Library veröffentlicht, die auch mit der Version 2.3.1 zurecht kommt. Sie basiert auf den Java Wrappern von Samuel Audet und heißt javacvPro.

Sie kann wie jede andere Processing Library einfach hier heruntergeladen und der Inhalt nach /home/user/processing/modes/java/libraries entpackt werden. Dann ist sie mit Sketch > Import Library > javacvPro zu importieren.

Installationsanleitungen für Windows, Mac und Linux:

http://codeanticode.wordpress.com/2011/11/21/opencv-2-in-processing/

Danach kann man alle Beispiele der Projektseite in Processing laufen lassen. z.B.:

Image

// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - octobre 2011
// Tous droits réservés - Licence GPLv3

// Exemple fonction canny()

import monclubelec.javacvPro.*; // importe la librairie javacvPro

PImage img;

String url="http://www.mon-club-elec.fr/mes_images/online/lena.jpg"; // String contenant l'adresse internet de l'image à utiliser

OpenCV opencv; // déclare un objet OpenCV principal

void setup(){ // fonction d'initialisation exécutée 1 fois au démarrage

//-- charge image utilisée ---
img=loadImage(url,"jpg"); // crée un PImage contenant le fichier à partir adresse web

//--- initialise OpenCV ---
opencv = new OpenCV(this); // initialise objet OpenCV à partir du parent This
opencv.allocate(img.width, img.height); // initialise les buffers OpenCv à la taille de l'image

opencv.copy(img); // charge le PImage dans le buffer OpenCV

//--- initialise fenêtre Processing
size (opencv.width()*2, opencv.height()); // crée une fenêtre Processing de la 2xtaille du buffer principal OpenCV
//size (img.width, img.height); // aalternative en se basant sur l'image d'origine

//--- affiche image de départ ---
image(opencv.getBuffer(),0,0); // affiche le buffer principal OpenCV dans la fenêtre Processing

//--- opérations sur image ---

//-- toutes ces formes sont possibles :
//opencv.canny(); // applique le filtre de canny sur le buffer principal OpenCV avec paramètres par défaut
//opencv.canny(100,200); //applique le filtre de canny sur le buffer principal OpenCV avec paramètres - noyau 3x3 par défaut
opencv.canny(1000,2000,5); //applique le filtre de canny sur le buffer OpenCV désigné avec paramètres

//opencv.canny(opencv.Buffer,100,400); //applique le filtre de canny sur le buffer OpenCV désigné avec paramètres - noyau 3x3 par défaut
//opencv.canny(opencv.Buffer,100,200,3); //applique le filtre de canny sur le buffer OpenCV désigné avec paramètres

//opencv.invert(); // pour dessin au trait noir sur blanc

//--- affiche image finale ---
image(opencv.getBuffer(),opencv.width(),0); // affiche le buffer principal OpenCV dans la fenêtre Processing

noLoop(); // stop programme
}

void  draw() { // fonction exécutée en boucle

}

Weichzeichnen (Blur)


Objekte weichzeichnen zu können ist, ein Muss, will man ansprechende Graphiken erstellen. Die Processing eigene weichzeichen-Funktion blur() hat einen entscheidenden Nachteil. Sie ist sehr langsam. Will man Objekte in einer Animation (in Echtzeit) weichzeichnen ist sie somit obsolet.

Mario Klingemann hat einen sehr schnellen Weichzeichen-Algorithmus geschrieben, welcher wiederum von PhiLho erweitert wurde, so dass er nun auch mit dem Alpha Kanal umgehen kann. Damit hat man die Möglichkeit auch mit transparentem Hintergrund zu arbeiten.

// ==================================================
// fastblur, reworked by PhiLho from the Processing Forum
// by Mario Klingemann http://incubator.quasimondo.com/processing/superfast_blur.php
// ==================================================
void fastBlur(PImage img, int radius){
if (radius<1){
return;
}
int w=img.width;
int h=img.height;
int wm=w-1;
int hm=h-1;
int wh=w*h;
int div=radius+radius+1;
int a[]=new int[wh]; // i've added this
int r[]=new int[wh];
int g[]=new int[wh];
int b[]=new int[wh];
int asum,rsum,gsum,bsum,x,y,i,p,p1,p2,yp,yi,yw; // and the asum here
int vmin[] = new int[max(w,h)];
int vmax[] = new int[max(w,h)];
int[] pix=img.pixels;
int dv[]=new int[256*div];
for (i=0;i<256*div;i++){
dv[i]=(i/div);
}

yw=yi=0;

for (y=0;y<h;y++){
asum=rsum=gsum=bsum=0; // asum
for(i=-radius;i<=radius;i++){
p=pix[yi+min(wm,max(i,0))];
asum+=(p>>24) & 0xff;
rsum+=(p & 0xff0000)>>16;
gsum+=(p & 0x00ff00)>>8;
bsum+= p & 0x0000ff;
}
for (x=0;x<w;x++){
a[yi]=dv[asum];
r[yi]=dv[rsum];
g[yi]=dv[gsum];
b[yi]=dv[bsum];

if(y==0){
vmin[x]=min(x+radius+1,wm);
vmax[x]=max(x-radius,0);
}
p1=pix[yw+vmin[x]];
p2=pix[yw+vmax[x]];

asum+=((p1>>24) & 0xff)-((p2>>24) & 0xff); // asum
rsum+=((p1 & 0xff0000)-(p2 & 0xff0000))>>16;
gsum+=((p1 & 0x00ff00)-(p2 & 0x00ff00))>>8;
bsum+= (p1 & 0x0000ff)-(p2 & 0x0000ff);
yi++;
}
yw+=w;
}

for (x=0;x<w;x++){
asum=rsum=gsum=bsum=0;
yp=-radius*w;
for(i=-radius;i<=radius;i++){
yi=max(0,yp)+x;
asum+=a[yi]; // asum
rsum+=r[yi];
gsum+=g[yi];
bsum+=b[yi];
yp+=w;
}
yi=x;
for (y=0;y<h;y++){
pix[yi] = (dv[asum]<<24) | (dv[rsum]<<16) | (dv[gsum]<<8) | dv[bsum];
if(x==0){
vmin[y]=min(y+radius+1,hm)*w;
vmax[y]=max(y-radius,0)*w;
}
p1=x+vmin[y];
p2=x+vmax[y];

asum+=a[p1]-a[p2]; // asum
rsum+=r[p1]-r[p2];
gsum+=g[p1]-g[p2];
bsum+=b[p1]-b[p2];

yi+=w;
}
}
}

Wie setzt man den Algorithmus ein?

Wie wir der ersten Zeile entnehmen können, verlangt die Funktion beim Aufruf ein PImage und einen Integer als Parameter. Wir können nun also ein *.jpg laden und dieses, bevor wir es am Bildschirm ausgeben, mit dem Algorithmus weichzeichnen.

Beispiel FotoBlur:   starte Applet

Image

PImage img;

void setup() {
img=loadImage("blume.JPG");
size(img.width, img.height);
}

void draw() {
img=loadImage("blume.JPG");
fastBlur(img,mouseX/5);
image(img, 0, 0);
}

Wollen wir aber von Processing gezeichnete Objekte weichzeichen, müssen wir diese in einen sog. FrameBuffer zeichnen. Siehe hochauflösende Bilder ausgeben.

Beispiel  Bluring selbst gezeichneter Objekte: Starte Applet

Hier wird ein Graphik-Objekt (ellipse()) in einen Offscreen Buffer gezeichnet und dieser dann mit fastBlur() weichgezeichnet.

Image


/*---------------------------------------------------------
By Thomas Koberger
https://lernprocessing.wordpress.com/
---------------------------------------------------------*/

PImage img;
PGraphics pg;

void setup() {
img=loadImage("blume.JPG");
size(img.width, img.height);
}

void draw() {
image(img, 0, 0);
pg= createGraphics(width, height, P2D);
pg.beginDraw();
pg.noFill();
pg.strokeWeight(8);
pg.stroke(50, 100, 250);
pg.ellipse(width/2, height/2, 200, 200);
pg.loadPixels();
fastBlur(pg,mouseX/10);
pg.endDraw();
image(pg, 0, 0);
}

Beispiel Agenten weichzeichnen: starte Applet

Hier werden Agenten in kleine FrameBuffer gerendert, weichgezeichnet und dann angezeigt. Außerdem wird das ganze Bild, jeweils weichgezeichnet und im nächsten Frame als Hintergrund verwendet.

ImageImage

SourceCode siehe Applet!

Hochauflösende Bilder ausgeben


Will man einen Sketch hochauflösend (z.B. für den Druck) ausgeben, kann man alles, was in einem draw() Durchlauf gezeichnet wird, in einen sog. offScreen Buffer zeichnen und dann als Graphik speichern. Diese Technik wird auch „Double Buffering “ genannt.

Dafür erstellt man erst ein PGraphics Objekt. Dafür gibt man Breite, Höhe und den Renderer an.

PGraphics pg = createGraphics(width, height, JAVA2D);

Image

// from http://amnonp5.wordpress.com/2012/01/28/25-life-saving-tips-for-processing/

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

void draw() {
background(255);
smooth();
strokeWeight(10);
fill(255, 0, 0);
ellipse(width/2, height/2, 200, 200);
}

void keyPressed() {
if (key == 's') {
save("normal.png");
saveHiRes(5);
exit();
}
}

void saveHiRes(int scaleFactor) {

// erstellt neues PGraphics Objekt
PGraphics hires = createGraphics(width*scaleFactor, height*scaleFactor, JAVA2D);

//alles ab hier wird in das PGraphics Objekt gezeichnet
beginRecord(hires);
//und skaliert
hires.scale(scaleFactor);
draw();
endRecord();
//das PGraphics Objekt wird als .png gespeichert
hires.save("hires.png");
}

Anstatt wie hier alle Zeichenvorgänge von draw() in das PGraphics Objekt zu rendern, kann man mit folgenden Anweisungen auch direkt darauf zeichnen.

hires.strokeWeight(10);PGraphics
hires.fill(255, 0, 0);
hires.ellipse(width/2, height/2, 200, 200);

Visualisierung aus Textanalyse


Inspiriert von einem Sketch von Diane Lange, in dem „Die Bürgschaft“ von Schiller visualisiert wird, wollte ich auch mal was in der Richtung machen. Der Rechner sollte bei mir aber kein Graphiker sein, sondern ein Maler. Mir war es auch nicht so wichtig, dass der Connex zwischen dem visualisierten Buch und dem Bild, das daraus entstehen sollte allzu deutlich hervor tritt. Es ging mir eher darum, dem Computer, basierend auf einfachen Textattributen (Anzahl der Wörter, Anzahl unterschiedlicher Wörter,  durchschnittl. Satzlänge, und einigen Verhältnissen daraus) schöne und sich durchaus voneindander unterscheidende Bilder generieren zu lassen.

Die Grundform ergibt sich aus Lissajous Figuren, deren Komplexität sich aus den Attributen des Textes ergibt. Es werden in 2 Schritten erst die stark weichgezeichneten Formen im Hintergrund gezeichnet. Das könnte man wahrscheinlich viel effizienter programmieren. Mir war das allerdings nicht so wichtig, da der Output ja ohnehin Bilder sein sollten. In einem zweiten Schritt werden dann die feinen Strukturen im Vordergrund gezeichnet, wobei hier alle unterschiedlichen Wörter einmal entlang der Form der Lissajous Figur platziert und dann mit dem nächsten Wort im Text verbunden werden. Diese Verbindungslinien werden nicht nur innerhalb der Figuren in schwarz, sondern auch über diese hinaus und dann in einem hellen Grauton gezeichnet.

starte Applet

Source Code findet sich im Applet!

PeasyCam


Um die Ansicht einer Szene oder eines Bildes zu verändern gibt es im Allgemeinen zwei Möglichkeiten.

  1. Man verschiebt das Objekt.
  2. Man verändert den Standort des Betrachters.

Gerade in 3D Umgebungen ist es oft kompliziert mehrere Objekte so zu verschieben und zu drehen wie man das beabsichtigt. Hier ist es meist einfacher den Standort des Betrachter, sprich die Kamera zu verschieben und zu drehen.

Und genau dafür ist die PeasyCam Library da: http://mrfeinberg.com/peasycam/

Man braucht der Kamera nur die Position und den Punkt, auf den sie zeigen soll mitzuteilen.

Installation: siehe Libraries

Allgemeines:

import peasy.*;
PeasyCam cam;

void setup() {

  cam = new PeasyCam(this, 0, 0, 0, 500);

}

Die PeasyCam kann nach dem Import der Library mit der Maus gesteuert werden.

Was bedeuten die fünf Parameter der PeasyCam?
PeasyCam(this … Bezug auf den aktuellen Sketch, x-Position des Zielpunkts, y-Position des Zielpunkts, z-Position des Zielpunkts, Radius der Kugel, auf der sich die Cam bewegt);

Mausparameter:

  • Links-Click und ziehen –>  drehen
  • Mausrad oder Rechts-Click und ziehen –> zoom
  • Mittel-Click und ziehen –> Schwenken
  • DoppelClick –> reset

Die Cam wird auf einer Kugel mit einem bestimmten Abstand platziert und dann um dem Punkt, auf den sie „zeigt“ gedreht.

Außerdem ist es auch möglich die Kamera mittels Programmanweisungen zu positionieren.

Beispiel: Kamera mit Maus bewegen

In diesem Beispiel zeichnen wir das Koordinatensystem aus 3D Basics und bewegen die Kamera mit der Maus.

Starte Applet

Image

import processing.opengl.*;
import peasy.*;

PeasyCam cam;

void setup() {
size(640, 640, OPENGL);
translate(width/2, height/2, 0);
cam = new PeasyCam(this, 0, 0, 0, 1000);
}
void draw() {
background(0);
textSize(20);
stroke(255);
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);
}

Beispiel: Ändern des Betrachtungspunktes mittels Code

Mit der PeasyCam Funktion lookAt(x,y,z) kann man den Punkt der Betrachtung ändern. Das machen wir in diesem Beispiel innerhalb des Events mousePressed().

lookAt(x,y,z);

Wie zeichnen zwei Punkte in unsere 3D Umgebung und zentrieren die Kamera dann bei MausClick links und rechts auf je einen dieser Punkte.

Es ist zudem möglich die Position und Rotation der Kamera mit getPosition() und getRotation() (als float Array) auszulesen und in der Konsole auszugeben.

Starte Applet

Image

import processing.opengl.*;
import peasy.*;

PeasyCam cam;

void setup() {
size(640, 640, OPENGL);
translate(width/2, height/2, 0);
cam = new PeasyCam(this, 0, 0, 0, 1000);
}
void draw() {
background(0);
textSize(20);
stroke(255);
drawAxes();
drawPoint();
}

//zeichnet die beiden Punkte
void drawPoint() {
pushStyle();
strokeWeight(3);
point(-400, 0, 0);
point(400, 0, 0);
popStyle();
}

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

void mousePressed() {
//Ändert den Punkt der Betrachtung
if( mouseButton==LEFT) cam.lookAt(400, 0, 0);
else cam.lookAt(-400, 0, 0);

//gibt die Position und Drehung der Kamera in der Konsole aus
println("Position x: "+cam.getPosition()[0]+
" y: "+  cam.getPosition()[1]+
" z: "+  cam.getPosition()[2]);
println("Rotation x: "+cam.getRotations()[0]+
" y: "+  cam.getRotations()[1]+
" z: "+  cam.getRotations()[2]);
}

Kinect Installation auf Ubuntu 12.04


Processing 2.0

Mit dem Kinect Sensor und Processing lassen sich wirklich tolle Sachen machen. Z.B.: 2 Arbeiten von Cedric Kiefer: unnamed-soundsculpture, magic story telling.

Buchtip:

Als guter Einstieg in englischer Sprache eignet sich : Making-Things-See.

Hardware:

Um die Kinect am Computer betreiben zu können braucht man eine externe Stromversorgung. Die gibt es auch im Bundle, welches ich auf die Schnelle im Amazon Webshop nicht mehr finden konnte. Es sollte aber nach wie vor verfügbar sein.

Installation:

Um unter Processing mit dem Kinect Sensor arbeiten zu können, braucht man einen Treiber inkl. Java/Processing wrapper. Hier gibt es im derzeit 2 Möglichkeiten.

  1. Den Treiber des OpenKinect Projekts mit einem Wrapper von Daniel Shiffman. Dieser funktioniert in der vorliegenden Version allerdings nur auf dem Mac.Er kann aber unter dieser Anleitung von Nikolaus Gradwohl auch für Linux kompiliert werden.
  2. Für alle Plattformen ist hingegen die simple-openni verfügbar. Die Installation unter Windows und Linux funktioniert lt. Anleitung. Einzig die Version des Treibers im Linux Paket geht aus der Anleitung nicht klar hervor. Hier muss die Version im /Ordner/kinect/….. installiert werden.Dann noch den Wrapper herunterladen und wie beschrieben im /libraries Ordner im Sketchbook entpacken und schon kanns mit dem Beipielcode von der Seite ans Testen gehen.Tipp: Auf Ubuntu 12.04 wird die Kinect nach dem Verbinden sofort von dem Programm Guvcview belegt. Damit das nicht passiert, muss die Datei /etc/modprobe.d/blacklist.conf als root mit der Zeile blacklist gspca_kinect angereichert werden.
Image
import SimpleOpenNI.*;

SimpleOpenNI  context;

void setup()
{
context = new SimpleOpenNI(this);

// enable depthMap generation
context.enableDepth();

// enable camera image generation
context.enableRGB();

background(200,0,0);
size(context.depthWidth() + context.rgbWidth() + 10, context.rgbHeight());
}

void draw()
{
// update the cam
context.update();

// draw depthImageMap
image(context.depthImage(),0,0);

// draw camera
image(context.rgbImage(),context.depthWidth() + 10,0);
}

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

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

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.

Visualisierung von länderbezogenen Daten


Mich interessieren Daten. Oft kann man Daten in Tabellenform aber schwer fassen. Deshalb habe ich versucht Daten die österreichischen Bundesländer zu visualisieren.

D.h. konkret möchte ich die Arbeitslosenquote der Bundesländer farblich als Österreichkarte darstellen. Dafür brauchen wir als erstes eine Karte Österreichs als *.svg Datei. Die ist schnell auf Wikipedia gefunden: Map of Austria.

Vorbereiten der Vektorgrafik

Ich habe diese Karte zwecks einfacherer Analyse in Processing leicht modifiziert. Das ist sehr einfach, da es sich bei .svg Dateien eigentlich nur um XML Dateien handelt. Man kann sie einfach in jedem Texteditor öffnen. Unter Linux geschieht das dann auch noch gleich mit Code-Highlighting. Folgende Tags wurden von mir einfach aus der Datei entfernt.

<title>Austria</title>
 <desc>Political map of Austria</desc>
 <filter id="f1">
 <feGaussianBlur in="SourceGraphic" stdDeviation="10"/>
 </filter>
 <path id="Shadow" filter="url(#f1)" ..........................................>

Modifizierte Datei:

Image

Data Mining

Das Aufspüren und Aufbereiten der Daten ist in diesem Fall sehr einfach. Die Statistik Austria bietet sie in verschiedenen Formen an. Ich beschränke mich vorerst auf die Quote der Bundesländer im Jahr 2010 und kopiere die ensprechende Tabelle.

Die erste Visualisierung

Die .svg Datei Österreichs wird geladen und dann in die einzelenen Pfade der Bundesländer zerlegt. Die Daten der Arbeitslosenzahlen werden in einem Array in einer Reihe, den Pfaden der Bundesländern entsprechend abgelegt. Dann wird jeder Pfad gezeichnet und entsprechend der ArbeitslosenQuote eingefärbt.

Starte Applet

Image

// PShape Objekte erzeugen
PShape austria;
PShape[] bundesl;

//Titel
String title="Arbeitslosenqote Bundesländer 2010";

// Daten von Statistik Austria
float[] arblQuote = {
3.9, //Burgenland
3.9, //Carinthia
3.6, //Lower_Austria
3.7, //Upper_Austria
2.9, //Salzburg
4.2, //Styria
2.8, //Tyrol
3.9, //Vorarlberg
7.3  //Vienna
};

//Grundfarbe der Visualisierung
int col=210;

void setup() {
size(900, 500);

//Vektorgrafik laden
austria = loadShape("Map_of_Austria.svg");

//Pfade der einzelnen Bundesländer extrahieren
bundesl = austria.getChildren();

//Formatierung des Originals deaktivieren
austria.disableStyle();
noLoop();
colorMode(HSB, 360, 100, 100);
textAlign(CENTER, CENTER);
}

void draw() {
background(0, 0, 100);
stroke(col, 20, 99);

//Zeichnen der Bundesländer
for (int i=0; i<bundesl.length; i++) {
//Füllfarbe festlegen
fill(col, arblQuote[i]*13, 100);
//Zeichnen der Pfade
shape(bundesl[i], 0, 0);
println(bundesl[i].getName());
}
//Titel
textSize(20);
text(title, width/4,height*1/20);
}

Legende:

Eine Legende wäre jetzt natürlich noch nett. Dafür sollte man natürlich auch noch den Farbbereich automatisch an den Wertbereich der Daten anpassen.

starte Applet

Image

/* @pjs preload="Map_of_Austria.svg"; */

// PShape Objekte erzeugen
PShape austria;
PShape[] bundesl;

//Titel and Source
String title="Arbeitslosenqote Bundesländer 2010";
String source=" Quelle: Statistik Austria";

// Daten von Statistik Austria
float[] arblQuote = {
3.9, //Burgenland
3.9, //Carinthia
3.6, //Lower_Austria
3.7, //Upper_Austria
2.9, //Salzburg
4.2, //Styria
2.8, //Tyrol
3.9, //Vorarlberg
7.3  //Vienna
};

//Grundfarbe der Visualisierung
int col=210;

void setup() {
size(900, 500);

//Vektorgrafik laden
austria = loadShape("Map_of_Austria.svg");

//Pfade der einzelnen Bundesländer extrahieren
bundesl = austria.getChildren();

//Formatierung des Originals deaktivieren
austria.disableStyle();
noLoop();
colorMode(HSB, 360, 100, 100);
textAlign(CENTER, CENTER);
rectMode(CENTER);
}

void draw() {
background(0, 0, 100);
stroke(col, 20, 99);

// Koordinatensystem speichern
pushMatrix();
translate(0, 30);

//Zeichnen der Bundesländer
for (int i=0; i<bundesl.length; i++) {
//Füllfarbe festlegen
fill(col, map(arblQuote[i],getMinValue(arblQuote),getMaxValue(arblQuote),20,100), 100);
//Zeichnen der Pfade
shape(bundesl[i], 0, 0);
println(bundesl[i].getName());
}
//Koordinatensystem repositionieren
popMatrix();

//Legende zeichnen
legende(10, 50, 2.8, 7.3);

text(source, width/2, height*19/20);
//Titel
textSize(20);
text(title, width/4, height*1/20);
}

void legende(int x, int y, float valMin, float valMax) {
pushMatrix();
translate(x+10, y+10);
fill(0, 0, 0);
text("Legende:", 30, 0);
translate(0, 25);

//Min und Max Werte für die Beschriftung finden
int minVal=floor(valMin);
int maxVal=ceil(valMax);
float step= (float) (maxVal-minVal)/5;

//Farbfelder und Beschriftungen zeichnen
for (int i =0; i<6; i++) {
fill(col, map(minVal+step*i,getMinValue(arblQuote),getMaxValue(arblQuote),20,100), 100);
rect (0, i*20, 20, 10);
String legValue = " %";
fill(0, 0, 0);
text( (float)round(((float) minVal+step*i) * 100 )/100+" %", 30, i*20);
}
popMatrix();
}

float getMaxValue ( float[] vals) {
float [] values= new float [vals.length];
arrayCopy(vals,values);
Arrays.sort(values);
return values[values.length-1];

float getMinValue ( float[] vals) {
float [] values= new float [vals.length];
arrayCopy(vals,values);
Arrays.sort(values);
return values[0];
}

Beschriftung:

Damit das Ganze einen echten Mehrwert gegenüber der konventionellen Erstellung in einem Grafikprogramm bietet, sollte das Programm die Grafik automatisch beschriften. Dafür müssen wir die in der original-.svg Datei englischsprachigen Titel durch deutschsprachige ersetzen.

Die Quoten und Namen der Bundesländer sollen in der Mitte der jeweiligen Pfade platziert werden. Dafür müssen wir die minimalen und maximalen x und y Koordinaten aller Punkte eines Pfades ermitteln. Dann zeichnen wir genau in die Mitte.

Starte Applet

Image

Änderungen zum Beispiel vorher:

  1. Hintergrundfarbe geändert, damit die Beschriftung besser lesbar wird.
    background(0, 0, 80);
    
  2. In draw() werden die Koordinaten der Beschriftungstexte ermittelt und dann gezeichnet.
    <pre>//Zeichnen der Quoten
    fill(360,0,100);
    float [] coords = new float [2];
    coords= getCoords(bundesl[i].getChild(0));
    textSize(18);
    text(  (float)round(((float) arblQuote[i]) * 100 )/100+" %", coords[0],coords[1]);
    
  3. Funktion zur Ermittlung der Koordinaten.
    float [] getCoords (PShape thisShape) {
    
    float xMax=0;
    float yMax=0;
    float xMin=100000;
    float yMin=100000;
    float [] coords = new float [2];
    
    //Ermitteln der maximalen und minimalen Pfad-Koordinaten
    for (int i =0; i<thisShape.getVertexCount(); i++) {
    if (thisShape.getVertexX(i)>xMax)  xMax=thisShape.getVertexX(i);
    if (thisShape.getVertexY(i)>yMax)  yMax=thisShape.getVertexY(i);
    if (thisShape.getVertexX(i)<xMin)  xMin=thisShape.getVertexX(i);
    if (thisShape.getVertexY(i)<yMin)  yMin=thisShape.getVertexY(i);
    }
    coords[0]=(xMin+xMax)/2;
    coords[1]=(yMin+yMax)/2;
    return coords;
    }
    

Interaktivität:

starte Applet

Image

    1. Erweitern der Datenbasis
      Damit in unserer Anwendung nun auch Entwicklungen sichtbar werden, verwende ich nicht nur die Daten aus dem Jahr 2010, sondern eine ganze Tabelle mit den Daten der Jahre 1995-2010, wie sie von Statistik Austriaveröffentlicht werden. Damit diese von Processing auf einfache Weise automatisch eingelesen werden können, habe ich eine .csv Tabelle der folgender Struktur erstellt:

      • unnötige Spalten und Zeilen gelöscht
      • die , durch . ersetzt
      • die Bundesländer-Beschriftung von links nach rechts kopiert
      • im Sketch Ordner als list.csv gespeichert

      Download: list.csv

      1995;1996;1997;1998;1999;2000;2001;2002;2003;2004;2005;2006;2007;2008;2009;2010;
      3.8;4.2;4.3;3.4;3.6;3.2;4.1;4.3;4.2;5.6;6;5;3.7;3.6;4.6;3.9;Burgenland
      3;2.9;3.4;3.5;3.5;3.1;3.2;2.7;3.4;4.6;4.8;4.4;3.9;3.4;4.2;3.9;Kärnten
      3.4;3.8;3.8;4.4;3.3;3;3.2;3.6;3.5;4.2;4.3;4;3.6;3.4;4.3;3.6;Niederösterreich
      3.2;3.7;3.5;3.5;3.5;3.1;2.9;3.1;3.3;3.7;4;3.2;3.2;2.6;4;3.7;Oberösterreich
      2.7;3.2;3.2;3.4;2.7;2.3;1.9;2.8;2.2;3.7;3.2;3.1;3;2.5;3.2;2.9;Salzburg
      3.5;4.1;4;3.8;3.2;3.2;3.7;3.8;4;3.7;4.1;3.9;3.7;3.4;4.6;4.2;Steiermark
      2.9;3.2;3.4;2.9;2.5;2.5;2.3;2;2.6;3.3;3.5;2.9;2.8;2.4;2.9;2.8;Tirol
      3.3;3.9;3.9;3.6;3.5;2.4;2.4;2.5;4.1;4.1;5.3;4.4;3.6;3.9;4.9;3.9;Vorarlberg
      5.3;5.9;6.3;6.3;5.7;5.7;5.8;7.3;7.8;8.9;9.1;8.8;8.3;6.7;7.5;7.3;Wien

    2. Automatisches Einlesen der Daten aus einer .csv -Datei
      Hier habe ich als Ausgansbasis ein Skript von che-wei wang verwendet: http://cwwang.com/2008/02/23/csv-import-for-processing/. Mit ein paar kleinen  Modifikationen liest es auch in unserem Programm die .csv-Datei ein. Und speichert die Daten in einem 2-dimenstionalen Array.
    3. Ermitteln der minimalen und maximalen Werte
      Das ist jetzt nicht mehr ganz so einfach, wie vorher, aber kein allzu großes Problem, das in setup() erledigt wird:

      //Ermitteln der minimalen und maximalen Werte für die Farbwahl
      for (int j=0; j<csv[0].length;j++) {
      for (int i=0; i<arblQuote.length;i++) {
      arblQuote[i]=parseFloat(csv[i+1][j]);
      }
      if (getMinValue(arblQuote)<minVal) minVal=getMinValue(arblQuote);
      if (getMaxValue(arblQuote)>maxVal) maxVal=getMaxValue(arblQuote);
      }
      

      Damit werden dann in weiterer Folge unsere Werte mit konstanten Farbwerten über alle Jahre hinweg beschriftet.

    4. Übertragen der Jahreszahlen und des aktuell gewählten Jahres in entsprechende Variablen
      //Auslesen der Jahre
      years=new int[csv[0].length-1];
      for (int i=0; i< csv[0].length-1;i++) {
      years[i]=parseInt(csv[0][i]);
      }
      selYear=0;
      
    5. In draw(): Daten des aktuellen Jahres übertragen.
      //Daten des aktiven Jahres laden
      for (int i=0; i<arblQuote.length;i++) {
      arblQuote[i]=parseFloat(csv[i+1][selYear]);
      }
      
    6. Für jedes Bundesland den Namen anzeigen, wenn sich die Maus darüber befindet. (Funktioniert beim Bundesland Tirol nur bedingt!)
      //Namen anzeigen
      if (bundesl[i].getChild(0).contains(mouseX, mouseY)) {
      fill(0, 0, 40);
      textSize(14);
      text(bundesl[i].getName(), coords[0], coords[1]+20);
      }
      
    7. Interface, für die Auswahl der Jahre
      int yearSelector (int [] years) {
      
      noFill();
      pushMatrix();
      strokeWeight(1.5);
      
      //Linie zeichnen
      stroke(0, 70, 90);
      line(width-5, 0, width-5, height);
      strokeWeight(1);
      
      //Beschriftung
      translate(width-50, -10);
      for (int i=0; i<years.length; i++) {
      translate(0, height/years.length);
      
      //Hervorheben des gewählten Jahres
      if (years[i]==years[selYear]) {
      fill(0, 70, 90);
      triangle(35, 0, 45, -10, 45, 10);
      textSize(24);
      fill(360);
      }
      else {
      textSize(12);
      fill(0, 0, 60);
      }
      text(years[i], 0, 0);
      }
      popMatrix();
      return selYear ;
      }
      
    8. Mausaktivität abrufen um damit das gewünschte Jahr zu wählen
      //Mausposition auswerten
      void mouseMoved () {
      if (mouseX > width-60 && mouseX<width) {
      selYear=(int) map(mouseY, 0, height, 0, years.length);
      }
      }
      

Und diverse kleinere Modifikationen, z.B.: die Anpassung der Textpositionen an die Länge des Textinhalts usw. Den gesamten Quellcode findest du im Applet.


	

updated

Avatar von Thomas Kobergerprocessing - tutorial

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

Dieses Beispiel liest die Farbinformationen jedes…

Ursprünglichen Post anzeigen 122 weitere Wörter

MySQL Datenbankanbindung mit PHP


Wie praktisch ist es doch, wenn man die in einem Script anfallenden Daten in einer Datenbank speichern kann. Dazu verwenden wir in diesem Fall eine MySQL Datenbank auf http://www.bplaced.net/. Auf der Plattform sind nach der Anmeldung bis zu 8 Datenbanken erstellbar.

Mein Beispiel sollte die Ergebnisse eines Facebook Users bei einem Online-Game (programmiert in ProcessingJS) zwecks Highscore Listenerstellung in einer Datenbank abspeichern.

Wir werden uns hier anschauen, wie man Daten mit der Hilfe von kleinen PHP Scripts von Processing aus in eine MySQL Datenbank übertragen kann. Das ist besonders bei der Verwendung von ProcessingJS praktisch, da man vom Browser aus keine Berechtigung hat, Dateien zu erstellen und zu verändern. Somit bleibt eigentlich nur der Weg über die Datenbank.

Also beginnen wir mit der Erstellung der Datenbank. Das kann auf bplaced.net sehr kompfortabel über phpmyadmin erledigt werden.

PhpMyAdmin Tutorial: http://www.vms-tutorial.de/wiki/PhpMyAdmin#Zugang_zu_phpMyAdmin

Ich will mich hier nicht weiter mit Datenbankgrundlagen beschäftigen. Einschlägige Tutorials sind überall im Netz zu finden. Zum Beispiel hier: PHP und MySQL Einführung

Datenbank erstellen

Wir erstellen also hier nun eine Datenbank laut Anleitung: http://eass.bplaced.net/4-Datenbanken.

Beliebiger Name, bei mir: „facebook„. Und darin eine Tabelle mit dem Namen „Ergebnisse„.

Image

In dieser Tabelle legen wir nun 4 Spalten an:

  1. ID: mit autoincrement, bewirkt eine eindeutige Kennung für jeden Eintrag
  2. fbid: speichert in userem Beispiel eine Zahl vom Typ bigint mit den jeweiligen Facebook-UserID’s
  3. name: Name des Facebook-Users als String
  4. score:  Das Ergebnis bei einem fiktiven Facebook-Spiel

Image

Datenbank füllen:

Wenn die Struktur der Datenbank steht können wir versuchen Daten mit PHP per get oder post über http in die Datenbank einzutragen.

Hilfreich ist hier diese Seite: http://www.tutorialspoint.com/mysql/index.htm. Sie enthält für alle wichtigen Aufgaben Quellcode in SQL und PHP. Mehr braucht man nur selten!

Vorsicht: XXXXXX ist mit entsprechenden Werten zu ersetzten!

<?PHP

//Variablen für den Verbindugsaufbau
$dbhost = 'localhost';
$dbuser = 'XXXXXX';
$dbpass = 'XXXXXX';
$dbname = 'kobe_facebook';

//Verbindung wird aufgebaut
$dblink = mysql_connect("$dbhost", "$dbuser", "$dbpass")  or die ('I cannot connect to the database because: ' . mysql_error());
mysql_select_db("$dbname");

//Variablen werden aus über GET entgegengenommen.
$fbid = $_GET['fbid'];
$name = $_GET['name'];
$score = $_GET['score'];

//Variablen werden in die Datenbank geschrieben
$result = mysql_query("INSERT INTO ergebnisse (fbid, name, score) VALUES ('$fbid','$name','$score')");

//Abfrage auf Fehler
if(! $result )
{
die('Could not enter data: ' . mysql_error());
}

//Datenbankverbindung wird geschlossen
mysql_close($dblink);
?>

So. Will man dieses Script mit ProcessingJS nutzen kann, muss man es im Sketchordner unter /template_js speichern. Ist der Ordner nicht vorhanden, kann er mit im Processing Menü mit –> JavaScript –> Show Custom Template erstellt werden. Wenn man nun den Sketch „Exportiert“, wird von Processing automatisch ein Ordner mit dem Namen applet_js erstellt, der dann alles nötige (auch den Inhalt von /template_js) enthält. Der Inhalt dieses Ordners wird jetzt per FTP (z.B.: mit Filezilla) auf bplaced hochgeladen.

Nun ist es möglich die Tabellen-Spalten fbid, name und score mit den von uns über http:// übertragenen Werten zu befüllen.

Ein entsprechender Request könnte so aussehen:

http://username.bplaced.net/folder/setfb.php?fbid=88888888&name=richard%20stallman&score=89

Wobei username der Benutzername bei pblaced und folder der Ordner auf pblaced ist, in dem sich das PHP-Script befindet.

Wenn eine Probeübertragung geklappt hat, kann man nun auch Scripte schreiben, die Daten auslesen, oder die Datenbank updaten.

Daten auslesen:

Damit man überhaupt, um bei unserem Beispiel HighscoreListe zu bleiben, entschieden werden kann, ob jemand schon registriert ist, oder nicht, nehmen wir eine kleine Änderung im Code vor (nach dem Verbindungsaufbau):

//Variablen werden aus über GET entgegengenommen.
$fbid = $_GET['fbid'];

//der Eintrag zur entsprechenden fbid wird aufgerufen
$result = mysql_query("SELECT * FROM ergebnisse WHERE fbid='$fbid'", $dblink);

//kein Eintrag zur fbid
if(mysql_num_rows($result)==0){
echo"noData;";
}

//Eintrag vorhanden
while($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
echo "isthere;";
echo $row['score'];
}

Wenn jemand schon in der Datenbank registriert ist, sollte nämlich kein neuer Eintrag erstellt werden, sondern ein Update stattfinden. Andernfalls wird ein neuer Eintrag erstellt.

Getestet kann das Script so werden:

http://username.bplaced.net/folder/fbidlookup.php?fbid=88888888

Update eines Datensatzes:

Ist zu einem bestimmten User schon ein Eintrag vorhanden und er übertrifft aber seine persönliche Bestmarke, dann soll der Eintrag score ein Update erfahren. Die Umsetzung ist hier so, dass das PHP Script ein Update durchführt, aber nur aufgerufen wird, wenn dies gewünscht ist.


//Variablen werden aus über GET entgegengenommen.
$fbid = $_GET['fbid'];
$name = $_GET['name'];
$score = $_GET['score'];

//Update des Wertes für score
$result = mysql_query("UPDATE ergebnisse SET score=$score WHERE fbid=$fbid");

//Falls das nicht funktioniert hat
if(! $result )
{
die('Could not enter data: ' . mysql_error());
}

http://username.bplaced.net/folder/updatefb.php?fbid=88888888&name=richard%20stallman&score=89

Kommunikation Processing mit PHP:

Processing bringt die Funktion loadStrings() mit, die  Ergebnisse einer solchen Anfrage in ein StringArray schreibt. Dieses Array kann dann mit Hilfer der split()- Funktion weiter zerlegt werden. So werden auch komplexe Abfagen mit relativ wenig Code analysierbar.

</pre>
//Aufruf des PHP Scripts fbidlookup mit der fbid als Parameter

String [] InList=loadStrings("http://username.bplaced.net/folder/fbidlookup.php?fbid="+uid);

// Ergebnis ausplitten
String[] list = split(InList[0], ';');
<blockquote>

Ausgabe des Scripts:
InList[0] = isthere;89

list[0]=“isthere“;

list[1]=“89″;

Geschafft. Nur eine Kleinigkeit: list[1] kann natürlich nicht mit einer float oder int -Variable verglichen werden, da sie vom Typ String ist.

Mit der Anweisung

parseFloat(list[1]

kann man mit dem Inhalt aber zum Beispiel eine float Variable füllen.

Fertig!

Ergänzung:

Hat man größere Datenmengen zu übertragen, wird das den Programmablauf empfindlich stören. Die Lösung wäre dann die Abfragen in einem eigenen Thread vorzunehmen.

Threads


Wenn man Daten zur Programmlauffzeit aus einer Datei oder gar dem Internet laden will, hat man ein Problem: Das Programm bleibt stehen und läuft erst dann weiter, wenn die angefragten Daten übermittelt worden sind. Das kann mit unter sehr lange dauern und der User muß warten. Das Programm ruckelt und die User Experience ist schlecht.

Als Lösung für dieses Problem gibt es in Java die Möglichkeit zeitintensive Code- Abschnitte in eigenen Programmabschnitten laufen zu lassen, und nur jeden Frame abzufragen, ob die Daten bereits verfügbar sind, oder nicht.

Sind diese verfügbar, werden sie abgerufen und verarbeitet. Sind sie es nicht, läuft das Programm einfach ohne sie weiter.

Dies ist auch in Processing möglich. Es bringt die Klasse (thread) schon mit. Die einfachste Implementierung würde dann wie folgt aussehen.

void setup() { size(200,200);
// Die Funktion eine Funktion soll in einem separaten thread ausgeführt werden
thread("eineFunction");
}

void draw() {

}

void eineFunction() {
// Diese Funktion wird einem eigenen Thread ausgeführt.
}

Damit kann man nun noch nicht soviel anfangen. Daniel Shiffman hat hier dokumentiert, wie man die Thread-Klasse von Processing so erweitern und adaptieren kann, um sie sinnvoll nutzen zu können.

Ich habe die Klasse dann um eine Abfrage mit der YQL (siehe Geodaten und mehr mit der YQL) erweitert:

Wir schreiben also unsere eingene Klasse, nämlich die SimpleThread-Klasse. Um die Funktionen der Thread Klasse von Processing nutzen zu können, erweitern wir diese mit folgender Code-Zeile:

class SimpleThread extends Thread {

Dann daklarieren wir darin unsere Variablen:

boolean running;           // Is the thread running?  Yes or no?
boolean available;         // Daten verfügbar?  Yes or no?
int wait;                  // How many milliseconds should we wait in between executions?
String id;                 // Thread name
int count;                 // counter
float lat, lng;            // Längen und Breitengrad
PApplet parent;            //Wir brauchen eine Instanz von Processing, damit hier seine Funktionen zur Verfügung stehen
XMLElement xmlResponse;

Einige von diesen ebnötigen wir, damit die Thread Klasse funktioniert, andere um unsere Abfrage laufen lassen zu können. Z.B.: das PApplet parent ist eine Instanz der Processing PApplet Klasse. Diese benötigen wir, um Processing-Funktionen in unserer Klasse nutzen zu können.

Dann folgt der Konstruktor, welcher definiert, welche Variablen bei der Erzeugung einer Instanz an die Thread Klasse übergeben werden müssen. Da ist unter anderem wieder unser PApplet dabei.

// Constructor, create the thread
// It is not running by default
SimpleThread (PApplet theApplet, int w, String s) {
wait = w;
running = false;
id = s;
count = 0;
parent = theApplet;
}

Mit dabei können die originalen Processing Thread-Funktionen überschrieben werden, indem wir sie mit eigenen Funktionen gleichen namens ersetzen und sie um unseren eigenen Code ergänzen.

Ein Thread muss gestartet und beendet werden. Dies machen wir mit void start() und void quit(). Bei der Startfunktion ist die Anweisung in der letzten Zeile interessant: super.start(); Sie ruft die Processing Thread Funktion start() auf, damit nach unseren eigenen Anweisungen auch alles aufgerufen wird, was nötig ist um einen Thread erfolgreich zu starten(),

// Overriding "start()"
void start () {
// Set running equal to true
running = true;
// Print messages
println("Starting thread (will execute every " + wait + " milliseconds.)");
// Do whatever start does in Thread, don't forget this!
super.start();
}
// Our method that quits the thread
void quit() {
System.out.println("Quitting.");
running = false;  // Setting running to false ends the loop in run()
// IUn case the thread is waiting. . .
interrupt();
}

Es folgt nun noch der für uns interessanteste Teil des Ganzen, nämlich die Funktion void run(), welche ausgeführt wird, wenn der Thread läuft. Dort kommen nun unsere Abfragen hinein.

// We must implement run, this gets triggered by start()
void run () {
while (running) {
//println(id + ": " + count);
count++;
loc=loc.replace(' ', '_');
loc=loc.replace(",", "%2C");
println ("loc: "+loc);
try {
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

xmlResponse = new XMLElement(parent, restUrl);
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();
lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};

available=true;

// Ok, let's wait for however long we should wait
try {
sleep((long)(wait));
}
catch (Exception e) {
}
}
System.out.println(id + " thread is done!");  // The thread is done when we get to the end of run()
}

Zu guter Letzt brauchen wir noch einige Funktionen, um mit unserem Thread kommunizieren und ihn steueren zu können. Alles zusammen sieht nun so aus:


SimpleThread thread1;

String loc;

void setup() {
size(200, 200);

//ein neuer Thread wird erzeugt
thread1 = new SimpleThread(this, 100, "a");
thread1.start();
loc = "vienna";
}

void draw() {
background(255);
fill(0);
println(thread1.available());
// wenn die Daten verfügbar sind, werden sie abgerufen und eine neue Abfrage übergeben
if (thread1.available()) {
println(thread1.getLat()+" " +thread1.getLng());
thread1.setLoc("paris");
}
}

class SimpleThread extends Thread {

boolean running;           // Is the thread running?  Yes or no?
boolean available;         // Daten verfügbar?  Yes or no?
int wait;                  // How many milliseconds should we wait in between executions?
String id;                 // Thread name
int count;                 // counter
float lat, lng;            // Längen und Breitengrad
PApplet parent;            //Wir brauchen eine Instanz von Processing, damit hier seine Funktionen zur Verfügung stehen
XMLElement xmlResponse;

// Constructor, create the thread
// It is not running by default
SimpleThread (PApplet theApplet, int w, String s) {
wait = w;
running = false;
id = s;
count = 0;
parent = theApplet;
}

float getLat() {
return lat;
}

float getLng() {
return lng;
}

int getCount() {
return count;
}

boolean available() {
return available;
}

void setLoc(String newLoc) {
available=false;
loc=newLoc;
}

// Overriding "start()"
void start () {
// Set running equal to true
running = true;
// Print messages
println("Starting thread (will execute every " + wait + " milliseconds.)");
// Do whatever start does in Thread, don't forget this!
super.start();
}

// We must implement run, this gets triggered by start()
void run () {
while (running) {
//println(id + ": " + count);
count++;
loc=loc.replace(' ', '_');
loc=loc.replace(",", "%2C");
println ("loc: "+loc);
try {
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

xmlResponse = new XMLElement(parent, restUrl);
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();
lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};

available=true;

// Ok, let's wait for however long we should wait
try {
sleep((long)(wait));
}
catch (Exception e) {
}
}
System.out.println(id + " thread is done!");  // The thread is done when we get to the end of run()
}
// We must implement run, this gets triggered by start()
void run () {
while (running) {
//println(id + ": " + count);
count++;
loc=loc.replace(' ', '_');
loc=loc.replace(",", "%2C");
println ("loc: "+loc);
try {
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

xmlResponse = new XMLElement(parent, restUrl);
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();
lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};

available=true;

// Ok, let's wait for however long we should wait
try {
sleep((long)(wait));
}
catch (Exception e) {
}
}
System.out.println(id + " thread is done!");  // The thread is done when we get to the end of run()
}
// Our method that quits the thread
void quit() {
System.out.println("Quitting.");
running = false;  // Setting running to false ends the loop in run()
// IUn case the thread is waiting. . .
interrupt();
}
}

Geodaten auf Karte einzeichnen


Wenn man Daten visualisieren will, geht es sehr oft darum Geodaten zu verarbeiten und zu modifizieren.

Die Geodaten holen wir uns über die YQL aus Placemarker. Die Koordinaten liegen dann in Form von 2 floats (Breiten– und Längengrad) vor.

Beim Zeichnen der Koordinaten auf einer (Welt-) Karte ergibt sich zunächst das Problem, dass man eine Kugeloberfläche nicht verzerrungsfrei auf einer ebenen Fläche abbilden kann. Es gibt sehr viele verschiedene Möglichkeiten, wie man mit dem Problem umgehen kann. Einen guten Überblick liefert diese Seite.

Ich habe mich für die (meiner Meinung nach) einfachsten Möglichkeit der Plate Carrée („plane square“ oder Plattkarte) Projektion entschieden.

Hier kann man die Längen- und Breitenkoordinaten direkt in x- und y Koordinaten umrechnen.

float x = map(longitude, -180, 180, 0, width);
float y = map(latitude, 90, -90, 0, height);
Mehr Info zur „Plattkarte“ gibt es auf Wikipedia.

Für diese Projekton hat Till Nagel eine eigene Placemarker-Klasse geschrieben, die ich für mein Projekt leicht verändert habe.

class PlaceMarker {

String name;
float lat;
float lng;

PlaceMarker(String name, float lat, float lng) {
this.name = name;
this.lat = lat;
this.lng = lng;
}

float getX() {
return map(lng, -180, 180, 0, width);
}

float getY() {
return map(lat, 90, -90, 0, height);
}

void display(int results) {

// Equirectangular projection
float x = map(lng, -180, 180, 0, width);
float y = map(lat, 90, -90, 0, height);
}

String toString() {
return name + " (" + lat + ", " + lng + ")";
}
}

try-catch Block


Ein sog. try-catch Block kann genutzt werden, um einen Codeabschnitt zu umschließen, der voraussichtlich einmal eine Fehlermeldung zurück geben wird. Dies ist z.B. bei Schreibvorgängen auf der Festplatte und noch viel eher beim Abrufen von Daten aus dem Internet der Fall.

Der try-catch Block kann einen Fehler (oder besser eine Ausnahme – Exeption) abfangen und so den Absturz des Programms verhindern.

try {
// hier läuft der fehleranfällige Code

} catch (Fehlerklasse Fehlervariable) {

// Fehlerbehandlung
};

Ablauf:

Passiert im try-Abschnitt wirklich ein Fehler (es wird eine Exception „geworfen“), wird die catch-Funktion aufgerufen und die Fehlermeldung in der Fehlervariable gespeichert. Nun hat man die Möglichkeit im Catch Abschnitt mit dem Fehler umzugehen (Abfrage nochmal starten, oder was auch immer….) und die Fehlervariable in der Konsole auszugeben, damit man dort auch sieht, dass ein Fehler passiert ist. Das Programm stürzt ja nicht mehr ab.

Praktisches Beispiel Internet Abfrage :

try {
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

xmlResponse = new XMLElement(parent, restUrl);
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();
float lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
float lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());

PlaceMarker placeMarker = new PlaceMarker(name, col1, col2, lat, lng);
placeMarkers.add(placeMarker);
println("MarkersSize: " + placeMarkers.size());
//println(i + ": " + placeMarker);
//println(i + ". " + name + " (" + lat + ", " + lng + ")");
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};

Man könnte jetzt auf die Idee kommen bei jeder Fehlermeldung einen try – catch Block einzusezen, um den Programmabsturz zu verhindern. Dies sollte man allerdings dringend vermeiden. Die Folge wäre Code, der zwar nicht macht was er soll, aber auch nicht abstürzt und vielleicht nicht einmal eine Fehlermeldung ausspuckt.

Fazit: try-catch Blöcke sollten nicht eingesetzt werden, um schlechte Programmierung zu verschleiern.

Geodaten und mehr mit Yahoo Query Language


Das wollten wir doch immer schon. Geodaten mit beliebigen Suchbegriffen verbinden und noch vieles mehr. Ein Service von Yahoo, nämlich die YQL (Yahoo Query Language) macht es möglich. Es handelt sich dabei um eine an die Syntax von SQL angelehnte Abfragesprache, die mittlerweile sehr vielen Datenbanken abfragen durchführen kann. Eine gute englischsprachige Erklärung zur YQL gibt es von Till Nagel.

Wir nutzen die YQL, um Ortskoordinaten abzufragen. Ein Account bei Yahoo ist dafür nicht einmal nötig.

Info über Datenbanken und eine Abfragekonsole zum Testen der Anfragen gibt es unter: http://developer.yahoo.com/yql/console/

Dort findet man unter den Beispielabfragen:

select * from geo.places where text=“vienna“

Als Antwort auf die Anfrage bekommt man folgende XML Datei:
ImageSie enhält im Tag centroid die gewünschten Daten, nämlich den mit latitude getagden Breitengrad und den mit longitude getagden Längengrad.

Diese wollen wir nun mit Processing auslesen. Dafür verwenden wir das Processing Objekt XMLElement (ab Processing 2.0 nur noch XML).

Folgende Processing Funktion gibt uns Längen- und Breitengrad zu einem gesuchten Ort zurück.

XMLElement xmlResponse;

void getLoc(String loc) {

// Da die Daten aus Twitter stammen, enthalten sie teilweise Leerzeichen und Beistriche, ersetzt werden müssen,
//um keine Fehlermeldung zu provozieren.
loc=loc.replace(' ', '_');
loc=loc.replace(",", "%2C");
println ("loc: "+loc);

//Es ist nicht sicher, dass der Ortsname auch tatsächlich existiert. Dann würde das Programm abbrechen.
// Um das zu vermeiden wird hier ein try catch Block verwendet.
try {

//Abfrage
String restUrl="http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.places%20where%20text%3D%22"+loc+"%22&diagnostics=true";

//Abfangen der Antwort
xmlResponse = new XMLElement(this, restUrl);

//Analyse der Antwort
XMLElement[] placeXMLElements = xmlResponse.getChildren("results/place");
// println("Found " + placeXMLElements.length + " places");
if (placeXMLElements.length>0) {
String name = placeXMLElements[0].getChild(2).getContent();

//Auslesen der Geodaten
float lat = new Float(placeXMLElements[0].getChild(10).getChild(0).getContent());
float lng = new Float(placeXMLElements[0].getChild(10).getChild(1).getContent());
}
}
catch (NullPointerException e) {
println("Couldn't launch request: " + e);
};
}

Link zum Placemarker Projekt: http://developer.yahoo.com/geo/placemaker/

Galerie


Twitter API für Processing


Processing 2.0

Eine sehr gute Beschreibung der Funktionsweise in englischer Sprache findet man hier: http://blog.blprnt.com/blog/blprnt/updated-quick-tutorial-processing-twitter.

1. Twitter Library herunterladen und im Sketch platzieren

Für die Kommunikation mit der Twitter API gibt es schon eine fertige Java Library, nämlich twitter4j. Du kannst „latest stable version“  hier herunterladen. Nutzen kann man die Library, nachdem man .zip Datei extrahiert und dann das Java Archiv twitter4j-core-2.2.5.jar in den (villeicht noch nicht vorhandenen) /code Ordner im Sketch Ordner kopiert hat. Man kann das auch bewerkstelligen, indem man die Datei einfach auf die Processing IDE zieht.

Link zur Javadoc von twitter4j.

Sehr interessant ist, dass es von der Library auch eine Android Version gibt. Falls das jemand getestet hat, bitte ich im Feedback.

2. Einen Entwickler Account bei Twitter erstellen

  • Gehe auf die Homepage https://dev.twitter.com/.
  • Klicke „create an App“ und dann „sign in“
  • Formular ausfüllen und Nutzungsbedingungen akzeptieren.
  • Nun bist du auf diese Seite gelangt:Image
  • Von dieser Seite brauchst du den Consumer key und das Consumer secret.
  • Klicke Create my access token
  • Nun hast du auch Access token und das Access token secret. Damit kannst du nun deinen Processing Sketch mit der Twitter API verbinden.

3. Sketch erstellen und twitter4j einbinden

import java.util.*;
import twitter4j.conf.*;

//Verbindung herstellen
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setOAuthConsumerKey("eigenerSchlüssel");
cb.setOAuthConsumerSecret("eigenerSchlüssel");
cb.setOAuthAccessToken("eigenesToken");
cb.setOAuthAccessTokenSecret("eigenesToken");

//Abfrage starten
Twitter twitter = new TwitterFactory(cb.build()).getInstance();
Query query = new Query("pink floyd now playing");
query.setRpp(5);

//tryCatch, damit das Programm nicht abstürzt, wenn die Abfrage
//mal schief geht
try {

  //Abrufen der Seiten mit den Suchergebnissen
for (int j=1; j<=15 ; j++) {
QueryResult result = twitter.search(query.page(j));
ArrayList tweets = (ArrayList) result.getTweets();

    //Abrufen der einzelnen Tweets
for (int i = 0; i < tweets.size(); i++) {
Tweet tweet = (Tweet) tweets.get(i);
String user = tweet.getFromUser();
String msg = tweet.getText();
long geo=tweet.getFromUserId();
Date d = tweet.getCreatedAt();

      //Abrufen des Heimatortes des Users
User USER = twitter.showUser(geo);
String location = USER.getLocation();

println("Page: "+j+"  "+i+": Tweet by " + user + " at " + d +"from: "+location );
}
}
}
catch (TwitterException te) {
println("Couldn't connect: " + te);
};

  • Mit der TwitterFactory stellt die twitter4j Library ein Objekt zu Verfügung, das eine Abfrage durchführen kann.Twitter twitter = new TwitterFactory(cb.build()).getInstance();
  • Mittels der Query kann die Abfrage spezifiziert werden.
    Query query = new Query(„pink floyd now playing“);
  • Mit query.setRpp(5) stellt man die Anzahl der Treffer pro Seite ein (maximal 100). Außerdem kann man mit query.since(Datum) und query.until(Datum) die Suche zeitlich eingrenzen.
  • Man kann maximal 15 Seiten mit max. 100 Treffern pro Seite abrufen.
    //Abrufen der Seiten mit den Suchergebnissen

    for (int j=1; j<=15 ; j++) {
  • Die Treffer einer Seite werden in einem QueryResult Objekt gespeichert und dann ine eine ArrayList kopiert.
    QueryResult result = twitter.search(query.page(j));

    ArrayList tweets = (ArrayList) result.getTweets();
  • Aus dieser ArrayList rufen wir dann Treffer für Treffer ab und extrahieren die benötigten Daten aus dem Tweet Objekt.

//Abrufen der einzelnen Tweets
for (int i = 0; i < tweets.size(); i++) {
Tweet tweet = (Tweet) tweets.get(i);
String user = tweet.getFromUser();
String msg = tweet.getText();
long geo=tweet.getFromUserId();
Date d = tweet.getCreatedAt();

  • Falls man noch den Heimatort des Posters abrufen möchte, kann man das mit diesem Code machen:

//Abrufen des Heimatortes des Users
User USER = twitter.showUser(geo);
String location = USER.getLocation();

Da dies die Heimatort Einträge aus dem UserProfil sind, bekommt man natürlich auch Einträge wie: „Zwischen Seite 299 und 300“. Damit muss man leben.

3D wie im Magischen Auge


Processing 2.0

Wer kennt sie nicht? Die 3D Bilder der Buchreihe „Das magische Auge“ die Anfang der 90er erschien. Bei den 3D Bildern handelt es sich um sogenannte Stereogramme. Das sind räumlich wirkende Darstellungen zu deren Betrachtung man keine weiteren Hilfsmittel wie etwa eine Rot- Grün 3D Brille benötigt. Hier ein sehr einfaches Beispiel:

Image

Wie funktioniert die Betrachtungstechnik?

Entspanne Deine Augen und blicke geradeaus, als wenn Du durch das 3D Bild hindurchgucken wolltest. Jetzt sollte sich normalerweise langsam ein räumliches Bild entwickeln. Versuche jetzt mit den Augen das 3D Bild scharf zu stellen ohne sich bewußt auf das Stereogramm zu konzentrieren. Diese Methode des Betrachtens erfordert bei einigen eine gewisse Übung. Also nicht verzagen wenn es nicht auf Anhieb klappt.

Hier noch einige Tipps damit sich der Erfolg schneller einstellt:

* Keine Lichtreflexionen auf dem Bildschirm.

* Kopf geradehalten und die Augen parallel zur Bildschirmhorizontale.

* Abstand zum Bildschirm sollte ca. 80 cm betragen.

* Konzentration auf markante Bildpunkte und durch unscharfes Sehen Bildpunkte benachbarter Perioden zur Überlagerung bringen.

Text aus: http://www.wer-kennt-wen.de/gruppen/weltweit/das-magische-auge-uk6hr5qx/

Übe die Technik mit folgenden Beispielbildern:

Über den Erfinder dieser Technik:

Aufbau des Auges:

Image

Urheber: Talos

Das Licht fällt durch die Linse und den durchsichtigen Glaskörper auf die Netzhaut. Dort wird von den Sinneszellen (Zäpfchen und Stäbchen) die Helligkeit und Wellenlänge (Farbe) des Lichts aufgenommen. Über den Sehnerv, in dem schon die ersten signalverarbeitenden Prozesse stattfinden, werden die mittlerweile in Nervensignale umgewandelten Reize zum Gehirn weitergeleitet. Dort findet dann der eigentliche Wahrnehmungsprozess statt.

Wie nimmt das Auge Tiefe wahr?

Lies auf Wikipedia nach wie die Binokulare Raumwahrnehmung (mit 2 Augen) zustande kommt!

3 Tiefenkriterien:

http://de.wikipedia.org/wiki/Raumwahrnehmung

Während die Parallaxe den Winkel zwischen zwei Geraden beschreibt, steht der Begriff Disparation für für den Unterschied zwischen den Bildern, die unsere 2 Augen von einem 3-dimensionalen Objekt liefern.

Wie erzeugen wir ein Bild mit 3-D Effekt – Ein Autostereogramm?

Was ist ein Autosteregramm?

Ein Autostereogramm ist ein 2-D Bild, das unser Gehirn, mit Hilfe seiner Eigenschaft immer nach korrespondierenden Netzhautpunkten zu suchen, dazu bringt ein 3-dimensionales Bild wahrzunehmen.

Dazu muss man allerdings seine Augen trainieren, denn das normale Verhältnis des Winkels der Augen zueinander und dem Fokus muss verändert werden. Dies gelingt nicht allen Menschen gleich gut. Außerdem können etwa 10 % der Menschen überhaupt keine 3-D Bilder sehen.

Die einfachste Form eines Autosterogramms besteht aus horizontal in gleichem Abstand angeordneten gleichen Objekten (Siehe Beispielbild oben).

Wie entsteht der Tiefeneindruck?

Die Tiefe der einzelnen Objektreihen wird durch deren (horizontalen) Abstand zu einander bestimmt („distance„). Dieser Abstand kann nun variieren. Diese Variation bezeichnen wir als „shift„, also die Abweichung vom Standard-Abstand.

In dieser Konstellation erscheinen die größeren Wolken unten(shift 0) als weiter entfernt, als die kleineren Wolken oben (shift 40). Man kann diese Methode auch ganz einfach mit Schrift erzeugen.

Image

Image

Betrachte die Buchstaben mit dem 3D Blick. Was kannst du beobachten?

Image
Aus Wikipedia: http://en.wikipedia.org/wiki/File:Stereogram_Tut_Clean.png by Fred Hsu

Demo: Hier siehst Du wie aus 2 gleichen Objekten im Bild ein virtuelles Objekt, das hinter der Bildebene liegt entsteht.
Bewege den Mauszeiger auf der Grafik auf und ab, um die Tiefe des virtuellen Objekts verändern.

 Aufgabe: Verändere das Programm so, dass die Flugrichtung und Geschwindigkeit der Vögel mit der Maus gesteuert werden können.

Der Processing Sketch als Download: Landschaft

Etwas schwerer: Schreibe selbst ein Programm, das ein Autostereogramm erzeugt.

Programm zur Aufgabe: starte Applet

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

float picSize=1; //standard Zoom Factor
ArrayList pics; //holds the Pic-Objects

void setup() {
size (1200, 800);
background(9, 139, 232);
smooth();

fill(0, 200, 0);

pics = new ArrayList();

//adds the Pics to the ArrayList (Path,distance,Start-X-Pos,y-Pos, Zoom-Factor
pics.add(new Pic (loadImage("tree1.png"), 210,0, 540,picSize));
pics.add(new Pic (loadImage("cloud3.png"), 210,0, 160,picSize));
pics.add(new Pic (loadImage("tree2.png"), 190,200, 400,3));
pics.add(new Pic (loadImage("birds1.png"), 170,0, 340,picSize));
pics.add(new Pic (loadImage("butterfly.png"), 160,50, 430,picSize));
pics.add(new Pic (loadImage("bird2.png"), 150,0, 590,picSize));
pics.add(new Pic (loadImage("cloud1.png"), 190,0, 100,picSize));
pics.add(new Pic (loadImage("cloud2.png"), 170,0, 80,picSize));
pics.add(new Pic (loadImage("cloud1.png"), 150,0, 10,picSize));
}

void draw() {

//draws the Pics
for (int i=0; i<pics.size(); i++) {
Pic pic = (Pic) pics.get(i);
pic.drawpic();
}
}

void keyReleased() {
if (key == DELETE || key == BACKSPACE) background(360);
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);
}

class Pic {

//Variables of Pic-Object
PImage pic;
int distance;
int startX;
int yPos;
float picSize;

//The Constructor
Pic (PImage pic, int distance, int startX, int yPos, float picSize) {
this.pic    = pic;
this.distance   = distance;
this.yPos = yPos;
this.picSize=picSize;
this.startX=startX;
}

//Methods of Pics
void drawpic() {
for (int i=10; i<width; i+=distance) {
image(pic, i-startX, yPos, distance*picSize, distance*7/10*picSize);
}
}
}

Einfache Ebene mit Wallpaper

Ersetzen wir nun die einzelnen Objekte mit einem Flächen füllenden Wallpaper, sehen wir, dass bei entsprechender Betrachtung eine Fläche zu sehen ist, die unter der realen Zeichenebene liegt.

Image

Aus Wikipedia: http://en.wikipedia.org/wiki/File:Stereogram_Tut_Clean.png by Fred Hsu

Wieder gilt: Je weiter die korrespondierenden Pixel auseinander liegen, desto weiter unter der Oberfläche wird der Bildabschnitt wahrgenommen.

3D Bild aus einer Depth Map:
Als Depth Map bezeichnet man ein Graustufen-Bild, welches die Tiefeninformation als Grauwert darstellt (dunkel…hinten, hell…vorne). Damit kann man nun nicht nur ganze Objekte in einer bestimmten Ebene platzieren, sondern jedes Pixel seiner Tiefeninformation entsprechend darstellen (stimmt eigentlich nicht ganz, ist zu diesem Zeitpunkt aber egal).

Image

Aus Wikipedia: http://en.wikipedia.org/wiki/File:Stereogram_Tut_Clean.png by Fred Hsu

Das funktioniert folgendermaßen: Der erste Steifen Wallpaper wird ganz links am Bildschirm gezeichnet. Beim Zeichnen des zweiten Streifens wird dann Zeile für Zeile zu jedem Pixel das entsprechende Pixel aus der Depth Map eingelesen. Ist das Pixel schwarz, wird das Pixel von der gleichen Position im 1.Streifen (rot) in den 2. Streifen (blau) kopiert (siehe Abb. oben!). Ist das Pixel in der Depth Map heller, wird im ersten Streifen (rot) ein Pixel weiter rechts kopiert.

Image
Dadurch wird das Bild Streifen für Streifen immer weiter verzerrt. Schöner wäre es vielleicht, wenn man von der Mitte ausgehend dann links und nach rechts verzerrt.

Wallpapers kann man relativ leicht selber herstellen, oder durch ein zufälliges Pixelmuster ersetzen. Depth Maps können aus 3D Programmen exportiert werden.

Mit einem Gimp Plugin können solche Depth Maps auch selbst hergestellt werden.

Es findet sich aber auch im Internet brauchbares Material. Z.B. hier: Muster, Depth-Maps und einige Stereogramme

Image

  • Oder mit einer veränderten Code-Zeile :

Image

Beispiel: starte Applet

/** 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 depthMap, wallpaper;//deklariert die Bild Variablen;

int [] [] depth = new int [1260] [600]; //hier wird ein zweidimensionales Array deklariert
color [] [] wall = new color [140] [600];
color [] [] actual = new color [140] [600];
int shift; //horizontale Verschiebung des Pixels
int x=0; // damit sich die
int xToTake; //Bildpunkt im vorigen Wallpaper Streifen, der gezeichnet wird

void setup() {
size(1260, 600,P2D);

//Einlesen der Depth Maps und speichern der Tiefeninfo als Int-Array
depthMap = loadImage("hai_grey.png");//weist der Variablen depthMap die Datei hai_grey.png zu
while(depthMap.width<1){} //pausiert den Sketch, bis das Bild geladen ist.
depthMap.loadPixels(); //sollte man aufrufen, bevor man die einzelen Pixel bearbeitet
for (int gridX = 0; gridX < depthMap.width; gridX++) {
for (int gridY = 0; gridY < depthMap.height; gridY++) {
// überträgt die Farbinfo eines Pixels auf die Variable farbe
color farbe = depthMap.pixels[gridY*depthMap.width+gridX];
// wandelt die Farbinfo in einen Grauwert um
int grauwert =round(red(farbe)*0.222+green(farbe)*0.707+blue(farbe)*0.071);
depth [gridX] [gridY] = grauwert;//speichert den Grauwert in unser Array grau
}
}

//Einlesen des Wallpapers
wallpaper=loadImage("hai_pattern.png");
while(wallpaper.width<1){} //pausiert den Sketch, bis das Bild geladen ist.
wallpaper.loadPixels(); //sollte man aufrufen, bevor man die einzelen Pixel bearbeitet
for (int gridX = 0; gridX < wallpaper.width; gridX++) {
for (int gridY = 0; gridY < wallpaper.height; gridY++) {
// überträgt die Farbinfo eines Pixels auf die Variable farbe
color farbe = wallpaper.pixels[gridY*wallpaper.width+gridX];
// wird die nächste Zeile statt der vorherigen einkommentiert, wird das Wallpaper Bild
// durch ein zufälliges Pixelmuster ersetzt
// color farbe = color ((int) random(0,255),(int) random(0,255),(int) random(0,255));
wall [gridX] [gridY] = farbe;//speichert den Grauwert in unser Array grau
}
}
}

void draw() {
strokeWeight(2);
for (int gridY = 0; gridY < depthMap.height; gridY+=1) {
for (int gridX = 0; gridX < depthMap.width; gridX+=1) {
if (depth[gridX] [gridY]>0) {
shift=floor( depth[gridX] [gridY]/(10));
xToTake= (gridX+shift)%wallpaper.width;
stroke(wall [xToTake] [gridY]);
point(gridX, gridY);
wall [gridX%wallpaper.width] [gridY]=wall  [xToTake] [gridY];
}
else {
stroke(wall [gridX%wallpaper.width] [gridY]);
point(gridX, gridY);
}
}
}
noLoop();
}

void keyReleased() {
if (key == DELETE || key == BACKSPACE) background(360);
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);
}

Processing Sketch als Download:  3D Hai

Array Lists


Processing 2.0

Im Gegensatz zu Arrays muss bei einer ArrayList am Anfang nicht festgelegt werden, wie viele Elemente sie später enthalten wird. Man kann ihre Größe jederzeit ändern, indem man Elemente hinzufügt oder löscht. Außerdem wird kein spezieller Datentyp angegeben. Erst wenn man Objekte aus der ArrayList ausliest, wird der Datentyp zugewiesen.

Wichtigste Unterschiede zu Arrays:

  • Variable Anzahl von Elementen
  • Die Methode size() gibt die aktuelle Anzahl der Elemente zurück
  • Mit add() werden Elemente ergänzt
  • Mit remove() werden gelöscht
  • und mit get() ausgelesen.

Unvollständiges Codebeispiel aus dem Projekt Korruption in der Demokratie:

.
.
// Objekt wird erstellt
ArrayList agenten;

void setup() {
.
.
// Die Variable agenten wird mit einer ArrayList befüllt
agenten = new ArrayList();
}

void draw() {

// Nacheinander erden alle Objekte der ArrayList ausgelesen
for (int i=0; i<agenten.size(); i++) {

//Hierbei muss die Art des Objekts in der ArrayList festgelegt werden.
Agent agent =(Agent) agenten.get(i);
agent.render();
}
for (int r=(int) random (8);r>0;r--) {

// Listeneintrag hinzufügen
agenten.add(new Agent(pnts[j].x, pnts[j].y, agenten.size()));
}
}

Korruption in der Demokratie


Korruption scheint in Österreich so allgegenwärtig zu sein, wie der Schimmel im Brotkasten einer Studenten-WG.

Wie „virtueller Schimmel“ mit Hilfe von Agenten erzeugt werden kann und wie dieser die Demokratie „befällt“, zeigt folgendes Beispiel:

Starte Applet

Image

Mit Hilfe der Geomerative Library wird die Schrift erzeugt und die Koordinaten der Konturpunkte gefunden und in ein Array gespeichert. Mit einem Mausklick werden dann Agenten erzeugt, welche eine begrenzte Lebensdauer aufweisen und sich in zufälliger Richtung fortbewegen. Trifft ein Agent nun auf einen Konturpunkt, dann „stirbt“ er. Davor erzeugt er aber noch 0-8 neue Agenten, die sich ihrerseits auf die Suche nach Punkten auf der Schriftkontur machen. So durchdringen sie nach und nach alle Buchstaben bis dahin unsichtbaren Buchstaben und der Schriftzug wird sichtbar.

import geomerative.*;

RShape shp, shp1, shp2;
RPoint[] pnts, pnts1;

String BspText = "Korruption durchdringt die ...";
String BspText1 = "Demokratie!";
String BspText2 = "Mausklick um zu starten!";

ArrayList agenten;

void setup() {
size(1400, 500);
strokeWeight(1);
smooth();
background(0);
translate(width/2, height*2/12);
agenten = new ArrayList();

RG.init(this);

// 3 Shape - Objekte werden erzeugt.
// Die Schrift mit dem Namen "Ubuntu-R.ttf" muss im data Ordner platziert werden
shp = RG.getText(BspText, "Ubuntu-R.ttf", width/60, CENTER);
shp1 = RG.getText(BspText1, "Ubuntu-R.ttf", width*8/70, CENTER);
shp2 = RG.getText(BspText2, "Ubuntu-R.ttf", width/80, CENTER);

// Punkte an der Schriftkontur finden
//Abstand der Punkte
RCommand.setSegmentLength (1);
//Modus
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);

// Die Shapes wirde gezeichnet
fill(180, 160);
shp.draw();

// und positioneiert
shp1.translate(0, height*7/12);
shp2.translate(0, height*9/12);
fill(180, 160);
shp2.draw();

// Finden der Konturpunkte
pnts = shp1.getPoints();

// Variation der einzelnen Punkte
for (int i=0;i<pnts.length; i++) {
pnts[i].x+= (int) random(-5, 5);
pnts[i].y+= (int) random(-5, 5);
}
}

void draw() {

translate(width/2, height*1/12);

for (int i=0; i<agenten.size(); i++) {
//jeder Agent wird gezeichnet
Agent agent =(Agent) agenten.get(i);
agent.render();
//jeder Agent muß die Position ändern
agent.move();

// entfernen von Agenten aus der ArrayList
if (agent.lifetime<=0) agenten.remove(i);

// Wenn ein Agent auf einen Konturpunkt trifft, wird ein punkt gezeichnet und
// es werden neue Agenten erzeugt

for (int j=0; j<pnts.length; j++) {
if (!agent.blocked && (int)agent.position.x== (int) pnts[j].x && (int)agent.position.y==  (int)pnts[j].y) {
stroke(100, 150, 255, 200);
point(pnts[j].x, pnts[j].y);
fill(100, 150, 255, 60);

for (int r=(int) random (8);r>0;r--) {
agenten.add(new Agent(pnts[j].x, pnts[j].y, agenten.size()));
}

// Damit jeder Punkt nur ein mal "besetzt" wird!
pnts[j].x=-10000;
pnts[j].y=-10000;
agenten.remove(i);
}
}
}
println(agenten.size());
}

// Um den Wucherprozess in Gang zu setzen!
void mousePressed() {
agenten.add(new Agent(mouseX-width/2, mouseY-height*1/12, agenten.size()));
}

class Agent {

// Variablen
PVector position;
PVector direction;
PVector start;
//definiert die Stärke der Richtungsänderung
float spin = 0.40;
int lifetime;
boolean blocked;
float lifetimeinit; // 3. Radius

//der Konstruktor für die Agenten-Klasse
Agent (float theX, float theY, float alifetime) {
start    = new PVector (theX, theY);
position=new PVector(theX, theY);
direction   = new PVector (10, 10);
direction.x = random (-1, 1);
direction.y = random (-1, 1);
lifetimeinit = alifetime;
if ((int)random(60/lifetimeinit)!=0) {
lifetime=(int) random(400, 600);
}
else {
lifetime=(int) random(0, 50);
}
blocked=true;
}

void render() {
stroke(255, 20);
fill(255);

point(position.x, position.y);
lifetime--;

if (((int)start.x != (int) position.x) &&((int)start.y != (int) position.y)) {
blocked=false;
}
}

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(map(mouseX,0,width,5,1));

position.add(direction);
}
}

Geomerative Library


Download: http://www.ricardmarxer.com/geomerative/

Diese Library erweitert die graphischen Möglichkeiten von Processing (2D). Sie bietet u.a. die Möglichkeit Punkte an der Kontur von Vektorgraphiken zu finden. In der .zip Datei befinden sich viele Beispieldateien mit Kommentaren.

Sehr hilfreich fand ich auch dieses Tutorial: http://freeartbureau.org/blog/2011/09/18/geomerative-tutorial/.

Mit RFont kann man eine Schrift (eine .ttf – Datei muss sich im /data Ordner des Sketches befinden!!!) laden. Diese wird dann mit einem String verknüpft und einer RGroup zugeordnet. Mit RGroup.getPoints() bekommt man dann die Konturpunkte. Ein Nachteil dieser Methode ist, dass es in weiterer Folge schwierig ist, auf einzelne Buchstaben zuzugreifen und diese dynamisch zu ändern.

Beispiel: Punkte an der Schriftkontur

Image

import geomerative.*;

RFont font;
String BspText = "A B";
RPoint[] pnts;

void setup() {
size(400, 200);
smooth();
stroke(255);
background(100);

// Hier wird die Library initialisiert und die zu verwendende
// Schrift erzeugt.
// Achtung: Schrift muss zuerst mit Tools --> Create Font
// erzeugt werden.
RG.init(this);
font = new RFont("FreeSans.ttf", 150, RFont.CENTER);

// Punkte an der Schriftkontur finden

//Abstand der Punkte
RCommand.setSegmentLength (5);
//Modus
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);

//RCommand.setSegmentStep(5);
//RCommand.setSegmentator(RCommand.UNIFORMSTEP);

//RCommand.setSegmentAngle(random(0,HALF_PI));
//RCommand.setSegmentator(RCommand.ADAPTATIVE);

if (BspText.length() > 0) {

// Erzeugen einer neuer Gruppe von Graphiken
RGroup grp;

//Zuordnen des Texts
grp = font.toGroup(BspText);

//Finden der Punkte
pnts = grp.getPoints();
}
}

void draw() {
translate(width/2, height*7/8);
for (int i=0; i<pnts.length; i++) {
//Zeichnen der Punkte
point(pnts[i].x, pnts[i].y);
}
}

Beispiel:   starte Applet

In diesem Beispiel müssen die Positionen einzelner Buchstaben einer Gruppe verändert werden. Deshalb wird hier nicht mit der RGroup – Klasse, sondern mit der RShape – Klasse gearbeitet.

Image


import geomerative.*;

RShape shp, shp1;
RPoint[] pnts, pnts1;

String BspText = "O c c u p y";
String BspText1 = "W A L L ST";

int occupylen, actframe;

void setup() {
size(1480, 800);
strokeWeight(1);
smooth();
fill(120, 50);
background(0);
translate(width/2, height*4/8);

// Hier wird die Library initialisiert und die zu verwendende
// Schrift erzeugt.
// Achtung: Schrift muss zuerst mit Tools --> Create Font
// erzeugt werden.
RG.init(this);

// 2 Shape Objekte werden zugewiesen
shp = RG.getText(BspText, "Ubuntu-R.ttf", width*8/70, CENTER);
shp1 = RG.getText(BspText1, "Ubuntu-R.ttf", width*8/50, CENTER);

// Punkte an der Schriftkontur finden
//Abstand der Punkte
RCommand.setSegmentLength (height/400);
//Modus
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);

// Shape wirde gezeichnet
shp.draw();

// für Abbruch nach einem Durchlauf
pnts = shp.getPoints();
occupylen=pnts.length;

// damit der Text Wall ST wie gewünscht dargestellt wird
shp1.translate(width/20, height*3/8);

//mit children[i] kann man in Shapes immer auf die Buchstaben zugreifen
shp1.children[4].translate(width/5, height/40);
shp1.children[4].scale(0.6, 0.6);
shp1.children[5].translate(width*2/9, height/40);
shp1.children[5].scale(0.6, 0.6);

shp1.draw();

// Hier werden die beiden Shapes zu einer zusammengefügt
shp.addChild(shp1);
pnts = shp.getPoints();
}

void draw() {

translate(width/2, height*4/8);

// damit gleich mit dem Zeichnen begonnen wird
actframe=frameCount+occupylen;

//für Abbruch nach einem Durchlauf der Konturpunkte
if (frameCount<pnts.length) {
for (int j=0; j<pnts.length; j++) {
//Zeichnen der Punkte
if (dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y)<random(0, height)
&& dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y)>height/30
&&(int)random(height/20)==3) {
stroke(map(dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y), height/160, height*3/4, 0, 255),
map(pnts[actframe%pnts.length].x-pnts[j].x, -height/2, height/2, 255, 0),
map(dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y), height/160, height*3/4, 255, 0),
map(dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y), height/160, height*3/4, 10, 0));
line(pnts[actframe%pnts.length].x+random(-height/160, height/160), pnts[actframe%pnts.length].y+random(-height/160, height/160), pnts[j].x+random(-height/160, height/160), pnts[j].y+random(-height/160, height/160));
}
}
}
}

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!

Easing


Um eine Bewegung nicht abrupt ablaufen zu lassen gibt es eine einfache Technik – das „Easing“. Wird ein Objekt nicht sofort an seinen Endpunkt bewegt, sondern nur einen bestimmten Prozentwert in dessen Richtung. Das führt zu einer harmonischen runden Bewegung.

Konkret wird der Unterschied in der x-Positionen von Objekt und Mauszeiger errechnet (dx). Wenn dieser Wert größer ist als 1, dann wird ein neuer Wert für die Objektposition berechnet (x+= dx*easing;).

Beispiel: eindimensionales Easing starte Applet
Image

float x = 0.0; //aktuelle x-Position des Objekts
float easing = 0.05; //Stärke der Dämpfung

void setup() {
smooth();
size(500, 100);
}

void draw () {
background(0);
float targetX = mouseX;
// Unterschied der x-Position von Objekt und Mauszeiger
float dx = targetX -x;
//abs gibt den ansoluten Wert einer Zahl aus
if (abs(dx) >1.0) {
x+= dx*easing;
}
ellipse(mouseX, 30, 40, 40);
ellipse(x, 30, 40, 40);
} 

Aufgabe: Erweitere das obige Programm so, dass auch eine Bewegung der Objekte in Y-Richtung möglich ist. starte Applet

Image

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

Libraries


Libraties sind Programme, genauer genommen Programmpakete, die Programmierer anderen Processing- Nutzern zur Verfügung stellen. Sie erweitern die ohnehin schon großartigen Möglichkeiten von Processing. Libraries gibt es für alle möglichen Einsatzbereiche. Einige sind schon in der Processing-Installation enthalten. Man nennt diese die Core-Libraries:

  • Video
  • Network
  • DXF Export
  • Java Skript
  • Minim Audio
  • OpenGL
  • OpenGL2
  • PDF Export
  • Serial I/O

Darüber hinaus gibt es noch eine ganze Menge sog. User-Libraries, die frei genutzt werden können, in Punkto Stabilität aber  nicht immer mit den Core- Libraries mithalten können.

Eine gute Übersicht findet man hier auf der offiziellen Processing-Seite.

Um eine Library in Processing zu verwenden muss sie immer separat mit der import– Anweisung in den Sketch eingebunden werden. Außerdem gibt in der Processing IDE auch die Möglichkeit das im Menü unter Sketch –> import Library –> zu erledigen.

import processing.pdf.*;

Installation anderer Libraries
Will man andere Libraries in Processing einbinden, lädt man diese aus dem Net, entpackt sie mit einem entsprechendem Programm und verschiebt sie in den /modes/java/libraries Ordner im Processing Verzeichnis.

Processing neu starten und man kann sie wie oben dann in den Sketch importieren.

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

Processing Sketch in eine vollständige Android-App einbinden


In diesem Tutorial wird gezeigt, wie man einen Procesing-Sketch in Eclipse zu einer vollständigen App mit Menü-Activity und einem Button ausbaut, der die Processing-Acitvity öffnet. Bei der Erstellung ist dabei die Processing-IDE auch weiterhin sinnvoll. Die Processing-Sketches werden weiterhin darin entwickelt und dann, wenn sie fertig sind exportiert. Dabei erstellt Processing im Sketchbook-Folder einen Ordner mit dem Namen Android. Darin sind im src Ordner die *.java – Dateien zu finden, die man dann in das Eclipse-Projekt importieren kann.

Vorbereitungen: Installation der Android SDK des ADT Plugins für Eclipse lt. Anleitung.Image

1. Wir erstellen mit Hilfe des Assistenten des ADT-Plugins ein neues Android Programm –> New Android Programm . Alle Felder ausfüllen und Create Projekt from existing Sample –> Skeleton App wählen. Das erstellt ein leeres Android Projekt mit einer Activity und allen Dateien, die sonst noch notwendig sind.

Die Ordner-Struktur sieht dann folgendermaßen aus:

  • src: enthält die Java-Quelltexte
  • gen: hier finden sich automatisch generierte Klassen, wie die R-Klasse
  • res: hier kommen die Resourcen hin
  • asset: weiterer Ordner für Ressourcen
  • im root liegt dann noch die Android Manifest.xmlImage

2. Wir kopieren die Processing .java – Exportdatei aus dem Sketchbook /src/.. -Ordner in den Eclipse- /src-Ordner. Danach werden wir von Eclipse auf einige Fehler hingewiesen. Das liegt daran, dass Eclipse die Processing- Library nicht automatisch einbindet.

3. Die Library processing-core.jar einbinden. Re. Maustaste auf Projektname –> Build Path –> Add External Archieves. Dann die im Processing-Exportordner unter Android/libs processing-core.jar.

4. Die neue Activity muss in die AndroidManifest.xml eingetragen werden. Und zwar innerhalb des Application-Tags

<application android:icon="@drawable/icon" android:label="@string/app_name">
 <activity android:name="Proctest"
 android:label="@string/app_name">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>
 <activity android:name=".Procactivity" />
 </application>

5. Zu guter Letzt müssen wir noch die Acitvity starten. Das machen wir, wie in Android üblich per Intent. Wir werden dafür einen Button in der Start-Activity erstellen und von dort aus die Processing-Activity starten. Dafür ergänzen wir die main.xml in /res/layout um die folgenden Zeilen:

<Button
 android:id="@+id/start"  
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"   
 android:text="@string/start"
 android:onClick="onClickStarten" />

Die erste Zeile android:id=“@+id/start“ bedingt, dass wir auch in der Datei stings.xml in /res/values die Zeile: <string name=“start“>Start</string> innerhalb des resources-Tags ergänzen müssen.

Dannach erweitern wir die, vom Android-Assistenten erstellte *.java -Datei um diese Zeilen innerhalb der onCreate-Funktion:

final Button buttonstart =
 (Button) findViewById(R.id.start);
 buttonstart.setOnClickListener(mStartenListener);

Dadurch wird unserem Button ein Listener hinzugefügt. In den folgenden Zeilen wird dann noch ein der Intent aufgerufen, der die Processing -Acitvity startet. Dieser Code muss nach der onCreate-Funktion aufgerufen werden.

private OnClickListener mStartenListener =
 new OnClickListener() {
 public void onClick(View v) {
 onClickStarten(v);
 }
 };

 public void onClickStarten(final View sfNormal) {
 final Intent i = new Intent(this, Procactivity.class);
 startActivity(i);
 }

Damit sind wir fertig und können nun eine Processing-Acitvity inkl. Optionsmenü von einer beliebigen Activity aus starten.

Hier noch der gesamte Quellcode zum Download: proctest.zip

Processing Apps für Android – Optionsmenü


Nachdem Optionsmenüs für Android-Apps enorm wichtig sind, hier ein Code-Beispiel, wie man eines erstellen kann.

Als Beispiel habe ich die Funktion Vieleck aus dem basic-Artikel Winkel und Wellen gewählt, in dem man dann die Eckenzahl mit Hilfe eines Optionsmenüs wählen kann. Die Funktion ist, wie in den anderen Artikeln auch, einfach gewählt, damit der Fokus auf den eigentlichen Inhalten bleibt. In diesem Fall das Optionsmenü.

  • Processing Sketches laufen in Android als Activity, d.h. die Processing-Klasse PApplet erweitert die Klasse Acitvity. In so fern stehen auch alle Methoden der Acitvity-Klasse zur Verfügung.
  • menu.add(0, DREI_ID, Menu.NONE, „3“); erzeugt einen Menüeintrag, wobei 0 die goupId, DREI_ID die itemId, Menu.NONE die Reihenfolge und „3“ den Titel des Menüpunkts definieren. Menu.NONE wird angegeben, wenn die Reihenfolge der Einträge egal ist
//Imports für die Anzeige des Menüs
import android.view.Menu;
import android.view.MenuItem;

int ecken = 5; //Anzahl der Ecken beim Start
int winkel;
float x1, y1, x2, y2;

//IDs der Menüeinträge werden zugewiesen
public static final int DREI_ID =  Menu.FIRST;
public static final int VIER_ID =Menu.FIRST+2;
public static final  int FUENF_ID = Menu.FIRST+3;
public static final int SECHS_ID = Menu.FIRST+4;
public static final int ACHT_ID = Menu.FIRST+5;

void setup() {
 smooth();
}

void draw() {
 fill(255, 20);
 rect(0, 0, width, height);
 vieleck(ecken, mouseX, mouseY, 10, 10);
}

void  vieleck (int seiten, int x, int y, int radiusX, int radiusY) {
 winkel = (int) 360/seiten;
 for (int grade=0; grade<360; grade+=winkel) {
 x1 =sin(radians(grade))*radiusX+(x);
 y1 =cos(radians(grade))*radiusY+(y);
 x2 =sin(radians(grade+winkel))*radiusX+(x);
 y2 =cos(radians(grade+winkel))*radiusY+(y);
 line(x1, y1, x2, y2);
 }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
 // hier werden die einzelnen Menüeinträge erzeugt
 menu.add(0, DREI_ID, Menu.NONE, "3");
 menu.add(1, VIER_ID, Menu.NONE, "4");
 menu.add(2, FUENF_ID, Menu.NONE, "5");
 menu.add(3, SECHS_ID, Menu.NONE, "6");
 menu.add(4, ACHT_ID, Menu.NONE, "8");
 return super.onCreateOptionsMenu(menu);
}

//wird aufgerufen, wenn ein Menüpunkt ausgewählt worden ist
@Override
public boolean onOptionsItemSelected(MenuItem item) {
 //die Auswahl wird über die ItemId des Menüpunktes überprüft
 switch (item.getItemId()) {
 case DREI_ID:
 ecken=3;
 break;

 case VIER_ID:
 ecken=4;
 break;

 case FUENF_ID:
 ecken=5;
 break;

 case SECHS_ID:
 ecken=6;
 break;

 case ACHT_ID:
 ecken=8;
 break;
 }
 return super.onOptionsItemSelected(item);
}

Das wars!

Bedingungen mit Switch – Case


Processing 2.0

Mit switch / case kann man wie mit if / else Bedingungen programmieren. Während bei Verzweigungen mit weniger als 3 Möglichkeiten if / else die richtige Wahl ist, verwendet man bei Verzweigungen mit vielem Möglichkeiten besser switch / case.

switch (Variable) {
case Wert der Variable:
Anweisung1;
break; //notwendig, sonst würde auch die Anweisung 2 ausgeführt!
case Wert der Variable:
Anweisung2;
break;
case Wert der Variable:
Anweisung3;
break;
default:
Anweisung4;

}

Interessant ist dabei, dass die Ausführung der Anweisungen separat mit dem Befehl break abgebrochen werden muss. Andernfalls werden alle weiteren Anweisungen ausgeführt, selbst wenn die Bedingungen dafür nicht erfüllt sind.

Die default: Anweisung kann optional verwendet werden, falls man eine Anweisung ausführen will, wenn alle anderen Bedingungen nicht erfüllt werden. Entspricht somit der else – Funktion.

Beispiel Mausposition 1: starte Applet

Image

void setup() {
 size(500, 200);
}
void draw() {
 // Nicht im Android Modus
 //cursor(ARROW); //macht den Auszeiger in Form eines Pfeils sichtbar
 background(200);
 switch (round(mouseX/100)) {
 case 0:
 rect(0, 0, 100, 200);
 break;
 case 1:
 rect(100, 0, 100, 200);
 break;
 case 2:
 rect(200, 0, 100, 200);
 break;
 case 3:
 rect(300, 0, 100, 200);
 break;
 case 4:
 rect(400, 0, 100, 200);
 break;
 }
}

Beispiel Mausposition 2: starte Applet

Image


void setup() {
  size(500, 200);
}
void draw() {
 // Nicht im Android Modus
 //cursor(ARROW);//macht den Auszeiger in Form eines Pfeils sichtbar
 background(200);
 switch (round(mouseX/100)) {
 case 0:
 rect(0, 0, 100, 200);
 case 1:
 rect(100, 0, 100, 200);
 case 2:
 rect(200, 0, 100, 200);
 case 3:
 rect(300, 0, 100, 200);
 case 4:
 rect(400, 0, 100, 200);
 } case
}

Um die Elementfolge von links nach rechts wachsen zu lassen, müsste man nur die case – Anweisungen in umgekehrter Reihenfolge platzieren.

Android Grundlagen


Android ist ein auf Linux basierendes Betriebssystem, das für die Anwendung auf mobilen Geräten optimiert ist. Da sich der Linux-Kernel relativ leicht an die Anforderungen verschiedener Hardware-Plattformen anpassen lässt, ist das anfangs nur für ARM -Prozessoren verfügbare Android heute auf vielen verschiedenen Plattformen anzutreffen. Es kommt u.a. in Fernsehern, Infotainment-Systemen in Autos, Spielekonsolen und vielem mehr zum Einsatz.

Image

Wie in der Abbildung (Quelle: Wikipedia) oben zu sehen ist, stellt der Linux- Kernel die Basis von Android dar.

Die Libraries (oder Bibliotheken, geschrieben in C/C++) stellen die vom Anwendungsrahmen (Application Framework) benötigte Funktionalität zur Verfügung.

Daneben steht die Android-Laufzeit (Runtime), deren Kernkomponente die Dalvik-VM (DVM) ist. Sie sorgt dafür, dass pro Anwendung ein eigener Systemprozess gestartet wird. Dieser enthält eine eigene Dalvic-VM und darin die Anwendung selbst. Dies hat den Vorteil, dass nicht das ganze Betriebssystem „hängt“, wenn eine Anwendung abstürzt. Was die Programmierung angeht, kann man Android vollständig in Java programmieren, man muss allerdings den Java-Code noch extra durch den Dx-Converter verarbeiten.In Summe sieht das das dann so aus:

(Java) IDE (Processing) –> Java-Compiler –> *.class-Datei –> Dx-Konverter –> *.dex

Der Anwendungs-Rahmen (Application-Framework) stellt jetzt die Basis für unsere Entwicklungsarbeit. Sie enthält viele Systemklassen, auf die wir aus unseren Processing-Anwendungen heraus zugreifen können. Processing-Programme laufen als Activity. Diese Activitys unterliegen einem Lebenszyklus, d.h. bestimmte Ereignisse im Lebenszyklus können als Trigger in unseren Programmen verwendet werden.

Alle Anwendungen (Applications) greifen auf den darunter liegenden Anwendungs-Rahmen zu.

Weiterführende Info: Android-Laufzeitumgebung.pdf

Processing Apps für Android X – Erstellen einer .apk-Datei aus .pde


Image

Nun, da wir das Projekt Pure Pong! soweit fertig gestellt haben, versuchen wir jetzt es zu veröffentlichen. Da es in Processing derzeit keine Möglichkeit gibt, das per Knopfdruck zu erledigen, müssen wir hier selber Hand anlegen. Dafür gibt es auch eine Step by Step Anleitung von Eric Pavey, die aber bei mir auf Ubuntu 10.10 nicht genau funktionierte. Sehr hilfreich war bei mir dann die Anleitung von Google selbst.

Deshalb hier noch meine Erfahrungen Schritt für Schritt:

  1. Processing Sketch Exportieren
    Dabei erhalten wir eine .pde -Datei im Projektverzeichnis im Sketchbook.
  2. Einen geheimen Schlüssel erstellen
    In Ubuntu 10.10 sind alle notwendigen tools schon installiert. sudo keytool -genkey -v -keystore NAMEDESPROGRAMMS-release-key.keystore -alias DEINNAMEHIER -keyalg RSA -keysize 2048 -validity 10000
    Bevor der Schlüssel erstellt wird, muss man noch einige Fragen beantworten, die bei der Erstellung eine Rolle spielen. Wenn das Programm keytool durchgelaufen ist, wird im aktuellen Verzeichnis eine Datei NAMEDESPROGRAMMS-release-key.keystore erstellt.

    Vorsicht: der Schlüssel muss unbeding gesichert werden, denn für etwaige Updates muss das neue Programm wieder mit dem originalen Schlüssel signiert werden.

  3. Das Programm compilieren
    Unter Ubuntu brauchte ich dafür nur in das Sketchbook-Verzeichnis von Pure Pong! und dann in den Ordner android. Mit dem Befehl

    ant release

    wird dann ein neues Unterverzeichnis /bin erzeugt in der sich ein Verzeichnis classes und folgende Dateien befinden classes.dex  PurePong_.ap_  PurePong_-unsigned.apk.

  4. Das Programm signierenROGRAMMS-release-key.keystore -alias DEINNAMEHIER -keyalg RSA -keysize 2048 -validity 10000
    Die PurePong_-unsigned.apk ist wie der Name schon sagt, nicht signiert. Das erledigen wir mit dem Programm jarsigner. Ein vollständiger Komandozeilen-Befehlt sieht dann etwa so aus:

    jarsigner -verbose -keystore NAMEDESPROGRAMMS-release-key.keystore /VOLLSTÄNDIGER_PFAD+NAME-unsigned.apk DEINNAMEHIER

    Man muss dann noch das vorher bei der Schlüsselerstellung gewählte Passwort eingeben und schon laufen einige Zeilen Code mit adding und signing auf der Konsole durch und schon ist die Sache erledigt! Eine Erfolgsmeldung oder ähnliches gibt es bei mir nicht.

  5. Erstellen der fertigen .apk -Datei
    Hier kommt das Programm zipalign zum Einsatz. Es befindet sich im Installationsverzeichnis der Android SDK im Unterordner tools. Von dort aus gestartet macht es aus unserer Datei eine fertige .apk- Datei.

    /PFAD ZU DEINEM HOME VERZEICHNIS/android-sdk-linux_x86/tools/zipalign -v 4 /VOLLSTÄNDIGER_PFAD+NAME-unsigned.apk GEWÜNSCHTER_NAME.apkWenn das Programm fertig ist hat man ein fertige .apk -Datei zur Verfügung

Zu guter Letzt meine purepong.apk zum Download (mit RechtsClick – Datei speichern unter)!!!

Veröffentlichen im Market – Vorbereitungen

Die oben beschriebene Art der Veröffentlichung ist sozusagen die „basic“ Variante. Will man seine App im Market platzieren, sollte man noch einige Schritte ergänzen. Eine englischsprachige Anleitung gibt es direkt von Google.

3 Dinge sind hier wichtig:

  • Die App muss mit einem Sicherheitsschlüssel signiert werden, der bis 2033 gültig ist. Das habe wir mit dem Schlüssel oben erfüllt, da wir das keytool mit einer -validity 10000 aufgerufen haben. Diese Einstellung erzeugt einen Schlüssel mit einer Gültigkeit von 10000 Tagen, also ~27 Jahren.
    Image
  • Die Datei AndroidManifest.XML muss unbedingt einen Eintrag android:versionCode und android:versionName enthalten. Diese werden von Processing automatisch beim Exportieren erstellt und man muss sich nicht darum kümmern, wenn man das nicht ausdrücklich will.
  • Weiters müssen im Application-Tag noch die Attribute android:label und android:icon gesetzt sein, was Processing ebenfalls schon automatisch erledigt.

Ich habe dann noch ein eigenes Icon erstellt. Hier muss man allerdings gleich 3 mit verschiedenen Auflösungen generieren, damit es möglichst vielen Geräten funktioniert. Alle sind im Sketchbook-Ordner des Projekts unter /android/res/drawable zu finden. Meine Empfehlung: Das größte Icon im Ordner /drawable-hdpi mit Gimp öffner und bearbeiten. Ist es so, wie man es sich vorstellt, kann man es dann nach unten skalieren und auch in die anderen Ordner mit den entsprechenden Auflösungen speichern.

Der Android Market bietet die Möglichkeit das Lizensierungs-Modell von Google zu verwenden. Ich werde das in diesem Fall nicht tun. Infos darüber gibt es hier: Lizensierung für den Android Market.

Veröffentlichen im Market

Um Apps auf dem Market veröffentlichen zu können, braucht man, wie nicht anders zu erwarten eine Konto bei Google. Damit kann man sich dann auf dieser Seite für den Market anmelden: http://market.android.com/publish

  • Hier ist dann erst einmal ein Formular auszufüllen, das unter anderem auch nach der Telefonnummer fragt! Typisch Google.
  • Weiters sind 25 USD zu bezahlen (per Kreditkarte)
  • Danach muss man nur noch alles akzeptieren, was Google verlangt.

Geschafft. Nun kann man seine App in den Market hochladen.

Image

Interessanterweise hatte ich in weiterer Folge die größten Probleme damit, einen Screenshot von meinem Game zu erzeugen. All gratis im Market verfügbaren Programme versagten auf meinem Phone. Ich habe dann den Inhalt einfach abfotografiert. Ist zwar nicht so schön, aber es funkt.

Geschafft: Game ist im Market unter: https://market.android.com/search?q=PurePong!

Probiert es aus, ich freue mich über jedes Feedback!

Android App Pure Ping Pong!


Processing 2.0

Mein erstes fertiges Projekt ist wieder mal Ping Pong, diesmal Pure Ping Pong!. Zum allgemeinen Verständnis siehe: Projekt Ping Pong. Die Version für Android ist eine schon recht deutlich veränderte.

  • Ich habe im Gegensatz zu Ping Pong für die Steuerung des Balls und der Spielerbalken jetzt PVector-Objekte verwendet.
  • Das Spiel nutzt Multitouch und ist ein reines Multiplayer-Game.
    • Durch das Bewegen des Balkens während der Ballberührung kann man den Balls seitlich beschleunigen.
    • Die Geschwindigkeit des Balls hängt von der Größe des Pointers während der Ballberührung ab. Er kann dadurch beschleunigt, oder gebremst werden.
  • Es wurden Vibrations-Effekte für das haptisches Feedback eingebaut.

Hier der Quellcode mit Kommentaren:

<pre>// Imports
import android.content.Context;
import android.app.Notification;
import android.app.NotificationManager;

// Setup vibration globals:
NotificationManager gNotificationManager;
Notification gNotification;
long[] gVibrate = {
0, 100
};

// Globale Variablen
int size;
int s2;
int balls=5;
int balls1=5;
int serve=2;
int frame=0;
PVector ballPos;
PVector ballDir;
int pPosx;
int pPosy;
int ppPosx;
int pPosx1;
int pPosy1;
int ppPosx1;
float ballspeed;
float ballspeed1;
float maxSpeed;
float minSpeed;
int h24;
int h48;
int hh6;
int h6;
int h4;
int w8;
int w6;
int blockdir=0;
color bg = color(0, 150, 255);
color pl = color(255, 170, 0);

PFont font;

void setup()
{
size(displayWidth, displayHeight);

//Damit sich die Ausrichtung des Displays nicht während des Spiels ändert
orientation(PORTRAIT);
maxSpeed=displayHeight/40;
minSpeed=displayHeight/200;
size=(int)height/24;
//hier die am öftesten vorkommenden Berechnungen
s2=(int) size/2;
h24=(int) displayHeight/24;
h48=(int)displayHeight/48;
hh6=(int)displayHeight-displayHeight/6;
h6=(int)displayHeight/6;
h4=(int)displayHeight/4;
w8=(int)displayWidth/8;
w6=(int)displayWidth/6;

// die PVector-Objekte werden erzeugt
pPosx = (int)width/2;
pPosy = hh6;
ppPosx = pPosx;
pPosx1 = (int)width/2;
pPosy1 = h6;

ppPosx1 = pPosx1;
ballPos = new PVector(width/2, height-h4);
ballDir = new PVector (0, -4);
textAlign(CENTER);
rectMode(CENTER);
font = createFont("Arial", 12);
stroke(180);

// Create our Notification Manager:
gNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Create our Notification that will do the vibration:
gNotification = new Notification();
// Set the vibration:
gNotification.vibrate = gVibrate;
}

void draw()
{
background(bg);
fill(180);
// für die Start-Einblendung
if (frameCount<120) {
translate(width/2, height/2);
textSize(w8);
rotate(PI/2);
text("PURE PING PONG!", 0, 0);
rotate(-PI/2);
translate(-width/2, -height/2);
}
else {  // hier läuft das Spiel
line(0, height/2, width, height/2);

//Zeichnet verbleibende Bälle für die Spieler
for (int i=1; i<=balls1; i++) {
ellipse(width/(balls1+1)*i, height/3, h48, h48);
}
for (int i=1; i<=balls; i++) {
ellipse(width/(balls+1)*i, height*2/3, h48, h48);
}

// wenn ein Spieler gewonnen hat
if (balls<=0 || balls1 <=0) {
textSize(w8);
translate(width/2, height/2);
if (balls>balls1) {
text("YOU WON !", 0, h4);
}
else {
rotate(PI);
text("YOU WON !", 0, h4);
rotate(-PI);
}
translate(-width/2, -height/2);
}
else {

// Bewegung des Balles
ballPos.add(ballDir);

//Begrenzung seitlich
if (ballPos.x < 0+s2 || ballPos.x > width-s2) {
ballDir.x *= -1;
}

// Begrenzung Spieler 1 und Fehler

// Ball ist im Bereich des Balkens
if ( int(ballPos.y) >= int(hh6-h48-s2) && serve==2) {

//Ball ist vor dem Balken
if ((ballPos.x > pPosx-w6 && ballPos.x < pPosx+w6) && blockdir>4) {

//Beschleunigung oder Verzögerung des Ball je nach Druckpunkt
ballDir.y *=-lerp(0.6, 3, ballspeed);

//Maximal und Minimalwerte für die Ballgeschwindigkeit
if (ballDir.y>0) {
ballDir.y=min(ballDir.y, maxSpeed);
ballDir.y=max(ballDir.y, minSpeed);
}
else {
ballDir.y=max(ballDir.y, -maxSpeed);
ballDir.y=min(ballDir.y, -minSpeed);
}

// Damit kann man dem Ball "Schnitt" verleihen
ballDir.x -=(ppPosx-pPosx)/3;

//Haptisches Feedback wenn der Ball getroffen wird
gNotificationManager.notify(1, gNotification);

//Damit verhindert wird, dass der Ball mehrmals die Richtung wechselt
blockdir=0;
}

//bei Fehler
else {
frame=0;

balls-=1;
serve=0;
}
}

// Begrenzung Spieler 1 und Fehler

// Ball ist im Bereich des Balkens
if ( int(ballPos.y) <= int(h6+h48+s2) && serve==2) {

//Ball ist vor dem Balken
if ((ballPos.x > pPosx1-w6 && ballPos.x < pPosx1+w6) && blockdir>4) {

//Beschleunigung oder Verzögerung des Ball je nach Druckpunkt
ballDir.y *=-lerp(0.6, 3, ballspeed1);

//Maximal und Minimalwerte für die Ballgeschwindigkeit
if (ballDir.y>0) {
ballDir.y=min(ballDir.y, maxSpeed);
ballDir.y=max(ballDir.y, minSpeed);
}
else {
ballDir.y=max(ballDir.y, -maxSpeed);
ballDir.y=min(ballDir.y, -minSpeed);
}

// Damit kann man dem Ball "Schnitt" verleihen
ballDir.x -=(ppPosx1-pPosx1)/3;

//Haptisches Feedback wenn der Ball getroffen wird
gNotificationManager.notify(1, gNotification);

//Damit verhindert wird, dass der Ball mehrmals die Richtung wechselt
blockdir=0;
}

//bei Fehler
else {
frame=0;

balls1-=1;
serve=1;
}
}

// Zeichnet den Ball
stroke(255);
fill(255);
ellipse(ballPos.x, ballPos.y, size, size);

//Zeichnet die Balken
stroke(pl);
strokeWeight(h24);
line(pPosx-w8, hh6, pPosx+w8, hh6);
line(pPosx1-w8, h6, pPosx1+w8, h6);
strokeWeight(0);
stroke(200);

// Bewegung Player 1 und 2
ppPosx=pPosx;
ppPosx1=pPosx1;
}

// Spielverzögerung bei Fehler
if (serve==0 && frame>30) {
ballDir.x=0;
ballPos.x=pPosx;
ballPos.y=height-h4;
if (serve==0 && frame>90) {
ballDir.y *=-1;

serve =2;
}
}
if (serve==1 && frame>30) {
ballPos.x=pPosx1;
ballPos.y=h4;
ballDir.x=0;
if (serve==1 && frame>90) {
ballDir.y *=-1;

serve =2;
}
}

// Verzögerung bei Fehler
frame++;

// Blockierung der Ballrichtung nach dem Rückprall am Balken
blockdir++;
}
}

//Funktion für Multitouch Events von
// Eric Pavey - www.akeric.com - 2010-10-24  (verändert)
//-----------------------------------------------------------------------------------------
// Override Processing's surfaceTouchEvent, which will intercept all
// screen touch events.  This code only runs when the screen is touched.

public boolean surfaceTouchEvent(MotionEvent me) {
int pointers = me.getPointerCount();

if (pointers>1) {
// Zuordnung der TouchPoints zum jeweiligen Spieler
if (int(me.getY(0))<height/2) {
pPosx=(int)me.getX(1);
pPosx1=(int)me.getX(0);
ballspeed = me.getSize(1);
ballspeed1= me.getSize(0);
}

else {
pPosx=(int)me.getX(0);
pPosx1=(int)me.getX(1);
ballspeed = me.getSize(0);
ballspeed1= me.getSize(1);
}
}
return super.surfaceTouchEvent(me);
}

Viel Spass!

Processing Apps für Android II – Multitouch


Processing 2.0

Ein interessantes Feature der Android- Programmierung ist auf jeden Fall die Möglichkeit Multitouch zu verwenden. Es gibt im Netz schon einige Implementierungen, von denen ich diese von Eric Pavey für mein Projekt Pong für Android genutzt habe. Dieser Beispielcode ist zum Testen hervorragende geeignet.

Das Kernstück verwendet den Processing surfaceTouchEvent und triggert ihn mit dem MotionEvent aus der Android-Library.

//-----------------------------------------------------------------------------------------
// Override Processing's surfaceTouchEvent, which will intercept all
// screen touch events.  This code only runs when the screen is touched.

public boolean surfaceTouchEvent(MotionEvent me) {
 // Number of places on the screen being touched:
 int numPointers = me.getPointerCount();
 if (int(me.getY(0))<height/2) {
  playerPos.x=me.getX(1);
  playerPos1.x=me.getX(0);
  ballspeed = me.getSize(1);
  ballspeed1= me.getSize(0);
 } else {
  playerPos.x=me.getX(0);
  playerPos1.x=me.getX(1);
  ballspeed = me.getSize(0);
  ballspeed1= me.getSize(1);
 }
 return super.surfaceTouchEvent(me);
}

Ich habe diesen Code für mein Projekt Pure Pong! genutzt. Man kann mit ihm dann die 2 Pointer für das Spiel dem richtigen Spieler zuordnen und in draw() ihre Koordinaten verarbeiten.

Processing Apps für Android III – Vibration


Auch die Vibrationsfunktion von Android-Handys kann man in Processing nutzen. Dabei muss man der Anwendung aber zuerst die Rechte dazu einräumen. Das geht unter Processing Menü –> Android –> Sketch Permissions –> Vibration. Oder in der Datei AndroidManifest.XML.

Dieser Code von Eric Pavey ist gut zum Experimentieren geeignet.

Im Projekt Pure Pong! wurde ein ganz leicht veränderter Code verwendet. Zuerst müssen die nötigen Lybraries importiert werden.

import android.content.Context;
import android.app.Notification;
import android.app.NotificationManager;

Dann werden die Objekte erzeugt.

NotificationManager gNotificationManager;
Notification gNotification;
long[] gVibrate = {0,100};

Dann erzeugt man das die eigentliche Benachrichtigung mit folgendem Code. Ich habe diesen in setup() platziert.

void setup() {
    .
    .
    .
  // Create our Notification Manager:
  gNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
  // Create our Notification that will do the vibration:
  gNotification = new Notification();
  // Set the vibration:
  gNotification.vibrate = gVibrate;
}

Irgendwo im Code, bei mir in draw() kann die Vibration dann aufgerufen werden.

gNotificationManager.notify(1, gNotification);

Processing Apps für Android I – Erste App erstellen


Processing 2.0

Hier die offizielle Dokumentation für Processing auf Android und die entsprechende Abteilung im Forum.

Auch das ist ein guter Erfahrungsbericht und eine sehr einfache Schritt für Schritt Anleitung.

Unterstützt werden Android-Versionen ab 2.3.3 (API 10).

  1. Zuerst muss die Android SDK heruntergeladen werden. Ich verwende Ubuntu 12.04 und habe das entsprechende Paket laut Anleitung installiert.
    Das ADT-Plugin für Eclipse muss nicht installiert werden. Dafür müssen aber im Schritt 4 der Installationsanleitung die folgenden Pakete installiert werden:

    • unter Available Packages –> Android Repository: SDK Platform Android 2.3.3, API 10

    Man kann auch die neueren Versionen der API oder gleich alles zusätzlich installieren.

  2. In Processing 2.0 Android-Mode aktivieren, Berechtigungen zu setzen usw.
  3. ImageWenn wir unser Processing 2.0 jezt starten und im Menü –> Android Mode aktiviert haben, können wir, mit Shift Play den Sketch starten , wird dieser automatisch im Emulator ausgeführt. Allerdings muss der Sketch vorher gespeichert werden. Es kann mal passieren, dass der Emulator nicht beim ersten Mal startet.
  4. Hier ein kleines Beispielprogramm zum Testen:
  5. void setup() {
    Imagesize(240,400);
    noStroke();
    fill(255);
    
    ellipseMode(CENTER);
    };
    void draw() {
    background(10,80,139);
    ellipse(width/2, height/2, 150, 150);
    };
  6. Damit man das Programm nun auf dem Android-Gerät laufen lassen kann, muss man die Anleitung hier durcharbeiten: http://developer.android.com/guide/developing/device.html
  7. Wenn das Gerät verbunden ist, kann man das Programm von oben in Processing 2.0 mit einem Klick auf Playam Gerät laufen lassen. Mit Shift Play ist es aber nach wie vor möglich, den Emulator aufzurufen!
  8. Einige Tipps zur Programmierung speziell für Android:
    • Will man 3D Sketches im Emulator laufen lassen, muss man die virtuelle Maschine im AVD Manager bearbeiten und unter Hardware die GPU emulation auf yes stellen. Dort können auch andere Hardwareoptionen aktiviert werden.
    • Viele Mobiltelefone unterstützen nicht die volle 24bit Farbtiefe. Dadurch kann es zu Darstellungsproblemen bei Farbübergängen kommen.
    • Die Verwendung von createFont() bringt speziell bei 2D Anwendungen Qualitätsvorteile gegenüber loadFont().
    • Statt size(width, height), ist es besser size(displayWidth, displayHeight) zu verwenden. Dann wird die Anwendung immer auf den vollen Bildschirm skaliert. Als 3D Renderer kann man P2D oder P3D verwenden.
    • Was Maus-Eingabe betrifft, können mouseX und mouseY wie gehabt verwendet werden, zusätzlich gibt es neue Variablen. motionX, motionY, pmotionX, pmotionY und motionPressure.
    • EventHandler für Key– und MouseEvents sind nicht verfügbar.
    • Wenn man die Bildschirm-Orientierung sperren will, kann man das mit orientation(PORTRAIT) oder orientation(LANDSCAPE) machen.
    • weiter info unter: http://wiki.processing.org/w/Android

Processing verwenden


Processing 2.0

Processing kann von der Projektseite heruntergeladen werden. Einfach auspacken und starten.

Image

In Processing 2.0 gibt es rechts oben einen Modus – Wahlschalter. Dieser bietet nach der Installation 3 Möglichkeiten:

  1. JAVA … Es wird ein Java Programm erstellt, das auf unterschiedlichen Plattformen in einem eigenen Programmfenster läuft.
  2. ANDROID … Es wird ein lauffähiges Programm für Android Devices erstellt. Diese können entweder im Emulator oder auf dem Device ausgeführt werden. Damit dieser Modus funktioniert ist allerdings die Installation der Android SDK notwendig! Eine Anleitung findest du hier! Details
  3. JAVASCRIPT … In diesem Modus wird der Java Programmcode in Javascript Code konvertiert. Dieser wird dann direkt mit Browser angezeigt, und kann in jede Webseite integriert werden. Details

In allen 3 Modi wird der Programmcode im Programmfenster geschrieben und kann über die Play-Taste ausgeführt werden.

Das Terminal-Fenster unten kann für Textausgaben benutzt werden.

Die Export-Funktion erzeugt je nach Modus ein ausführbares Programm für Desktops, eine Android App oder eine HTML Datei mit Javascript Elementen.

Die Programmstruktur


Processing 2.0

Ein klassisches Processing-Programm besteht aus 2 Funktionen. setup() und draw(), wobei der Code in setup() einmalig und der Code in draw() kontinuierlich ausgeführt wird. Jeder Durchlauf von draw() zeichnet ein Frame in das Anwendungsfenster.

Beispiel: Applet starten

void setup() {
  size(300,300);
  /*legt dir größe
  des anwendungsfensters fest*/
}

void draw() {
  rect(50,50,100,100); // zeichnet ein rechteck auf den schirm
}

Image

Processing basiert auf Java und übernimmt auch dessen Syntax.  Es wurde aber für eine bessere Usability bei grafischen Aufgaben um einige Elemente erweitert.

Kommentare: // einzeiliger Kommentar, /* */ mehrzeiliger Kommentar siehe Beispiel oben Anweisungstrennzeichen: „;“ – jede Programmanweisung sollte mit einem Strichpunkt abgeschlossen werden.

Funktionen: über sie kann man bestimmte Aktionen ausführen. Bsp: size(300,300). Funktionen werden immer von zwei runden Klammern gefolgt, innerhalb derer verschiedene Parameter zu finden sein können. Parameter können aber auch fehlen, Bsp: smooth();. Es ist auch möglich eigene Funktionen zu schreiben und diese dann in anderen Teilen des Programms aufzurufen, was die Übersichtlichkeit des Programmcodes deutlich erhöht.

Ausdrücke: Sind vielleicht aus der Mathematik bekannt und auch mit diesen zu Vergleichen. Ein Operator kombiniert dabei verschiedene Werte.

Bsp:

Ausdruck                    Wert
7                                       7
3+8                                 11
(12,8+179)*8         1534,4
7>5                               true
6<3                               false

Konsolenfenster: Um wichtige Werte auszugeben, die sich z.B. im Anwendungsfenster zu schnell ändern, kann man die Konsole benutzen. Bsp.: print(), oder println() Das folgende Beispiel gibt eine 10 im Konsolenfenster aus.

int x=10;
println(x);

Zusätzliche Leerzeichen sind egal. Siehe Beispiel oben.

Groß- Klein- Schreibung beachten!!!

Das Koordinatensystem


Processing 2.0

In Processing wird von jeder Anwendung ein Fenster erzeugt, dessen Größe von der Funktion size(x,y) bestimmt wird. Der erste Wert x steht für die Anzahl der horizontalen Pixel, der 2. Wert y für die Anzahl der vertikalen.

 size(640,480); //erzeugt somit eine Fenster mit 640*480 Bildpunkten 

Auch alle anderen Funktionen, nutzen ein Koordinatensystem. Jeder Punkt wird von einer x- und einer y- Koordinate bestimmt.

 point(200,200); //zeichnet einen Punkt auf 200 horizontal und 200 vertikal

Image

Will man in 3D arbeiten, so kommt eine z-Achse hinzu. Siehe dazu: 3D Basics

Um das Programmfenster skalierbar zu machen verwendet man das Objekt frame. Dazu vielleicht später mehr. Durch die Zeile frame.setResizable(true); in setup() erhält man ein Programmfenster, das mit der Maus skaliert werden kann. Achtung funktioniert nur in Java Mode!

Einfache Formen


Processing 2.0

Punkt:

point(x,y);

Applet startenEin Punkt ist 1 Pixel groß und durch seine Position auf der x- und der y-Achse bestimmt.

Beispiel:Applet starten

Image

point(10,20); //erster Punkt links oben, letzer
point(20,30);//rechts unten
point(30,40);
point(40,50);
point(50,60);

Punkte außerhalb des Displays werden nicht als Fehler behandelt, sondern einfach nicht angezeigt.

Übung: Schreibe ein Programm, das eine vertikale Linie aus Punkten von (10,10) bis (10,20) ausgibt.

Linie

line(x1,y1,x2,y2);

Zeichnet eine Linie von einem Anfangspunkt zu einem Endpunkt. Die Strichdicke kann nicht direkt in der Funktion line() eingestellt werden. Dafür gibt es die Funktion strokeWeight().

Beispiel: Applet starten
Image

 strokeWeight(2);//legt die Linienstäre auf 2 Pixel fest

line(10,10,90,90); // zeichnet eine Line von (10,10) nach (90,90)

strokeWeight(4); line(90,10,10,90);

Dreieck

triangle(x1,y1,x2,y2,x3,y3);
Zeichnet ein Dreieck zwischen den 3 Eckpunkten. Dieses kann, wie auch andere 2-dimensionale Objekte gefüllt werden.  Dafür wird die Funktion fill() verwendet. Als Parameter für fill() kann entweder ein Grauwert (fill(255) wäre weiß), oder eine beliebige Farbe lt. RGB-Modell verwendet werden. fill(r,g,b), wobei r,g und b für die Rot, Grün und Blau-Werte von 0-255 stehen. Es können aber auch 2 Parameter angegeben werden, wobei der 2. für die Transparenz steht (0 – nicht durchsichtig, 255 völlig durchsichtig).

Siehe RGB Modell auf Wikipedia.

Beispiel: Applet starten
Image

fill(227,16,16); //legt dir Füllfarbe für das
 // hintere Dreieck fest
 triangle(10,10,90,10,70,70); //hinten
 fill(255,230);//der 2. Wert definiert die Transparenz
 triangle(10,40,90,40,70,100);//vorne

Das Beispiel zeigt gut, dass jene Objekte, die im Code weiter unten generiert werden dann in der Darstellung im Vordergrund zu finden sind.

Viereck

quad(x1,y1,x2,y2,x3,y3,x4,y4);

Mit dieser Funkton lassen sich alle Arte von Vierecken erzeugen, auch Parallelogramme und irreguläre Vierecke.

Rechtecke

rect(x,y,width,height);

Rechtecke werden mit Hilfe des linken oberen Eckpunktes (x – und y- Koordinaten), der Breite und der Höhe bestimmt.

Übung: Erstelle ein Bild wie das unten. Verwende dafür die Funktionen size(), fill(), und rect(). Image

Ellipse

ellipse(x,y,width,height);

Zeichnet Ellipsen und Kreise. Kreise entstehen dann, wenn widht und height gleich groß sind.

Übung: Erweitere die Abbildung oben mit ein paar runden Elementen.

Bezier

bezier(x1,y1,cx1,cy1,cx2,cy2,x2,y2);

Mit der Bezier- Funktion kann man Kurven erzeugen. Dies erfordert aber einige Übung. Prinzipiell funktioniert es wie die Vektor-Linienfunktion in Grafikprogrammen. Man definiert einen Punkt und lenkt durch einen 2. Punkt, der nicht Teil der Kurve ist die Linie ab.

Beispiel: Applet starten

Image

 void setup () {
 size(640,480);
 }
 void draw (){
 bezier(100,100,mouseX,mouseY,mouseX,mouseY,400,100);
 } 

Hier kann mit der Maus cx1,cy1, cx2 und cy2 verändern und bekommt so einen Eindruck wie Bezier- Kurven entstehen.

Hintergrund

background();

Diese Funktion legt die Hintergrundfarbe fest. Sie kann genau wie fill() als Parameter einen Graustufenwert oder aber Werte nach RGB-Modell beinhalten.

Hinweis: Wenn man die RGB- Werte von bestimmten Farben sucht, kann man unter Tools>ColorSelektor ein Fenster aufrufen, dass die RGB Werte von individuell ausgewählten Farben anzeigt.

Sonstiges:

noFill() verhindert das Füllen von 2 und 3-D Objekten, es werden nur die Konturen angezeigt.

noStroke() verhindert das Anzeigen der Ränder von Objekten.

smooth() aktiviert das Antialiasing – Kanten werden geglättet. In Processing 2.0 standardmäßig aktiviert!

noSmooth() deaktiviert Antialiasing

einstieg in die programmierung mit processing

Erstelle eine Website wie diese mit WordPress.com
Jetzt starten