Willkommen auf unserem Seminar-Blog

Immer auf dem aktuellen Stand bleiben

Dieser Seminar-Blog befindet sich noch im Aufbau und wird in den kommenden Tagen entsprechend verfeinert.

Member Login

Lost your password?

Registration is closed

Sorry, you are not allowed to register by yourself on this site!

You must either be invited by one of our team member or request an invitation by email at viad.info {at} zhdk {dot} ch.

Inklings

Ideenfindung

Während der Ideenfindung haben wir uns für 3 verschiedenen Varianten entschieden, wobei wir die Dritte als Konzept umgesetzt haben und weiter verfolgen werden:

1. Spielerisches Lernen:

Ein Spielbrett welcher mit dem Computer verbunden ist dient als Spielbrett. Auf dem Computer wird ein Lerninhalt abgefragt. Der Benutzer hat für jeden Lernmodus ein Set von Spielkarten, welche als Antworten dienen. Durch das Auf-den-Tisch-Legen der Karten, wird ein Visuelles Feedback vom Computer gegeben ob die Antwort richtig oder falsch ist.

2. Gehör-Ballspiel

Zwei Pingpongschläger dienen als Spielgerät. Speziell hier ist, das es keinen Ball gibt. Die Ballposition kann lediglich von dem aktuellen Ton der aus einer Box kommt ermitteln, welche mit den Schläger verbunden ist. Je fester der Ball geschlagen wird desto höher fliegt er und desto intensiver wird die Tonkurve.

Konzept

Inklings ist eine kollaborative generative Installation. Auf dem Bildschirm wird eine Tintenlandschaft angezeigt, die durch vier verschiedene Controller beeinflusst werden kann:

  • Stab: Durch Schwingen beeinflusst der Stab die Bewegungsrichtung der Tintenwolken. (Beschleunigungssensor)
  • Box: Die Box enthält kleine Kügelchen und kann geschüttelt werden. Sie erzeugt damit einen Zufallswert, der die Menge der Wolken festlegt. (Piezo-Sensor)
  • Zylinder: Der Zylinder besteht aus zwei Zylindern, die um eine Mittelachse in zwei Richtungen gedreht werden kann. Er beeinflusst die Drehung der Tinte. (Potentiometer)
  • Ball: Der Ball kann gequetscht werden und erzeugt rote Tintenwolken. (Flex-Sensor)

Die Benutzer wissen in jedem Moment, wie sie die Tintenlandschaft mit ihrem Controller beeinflussen. Visuell sind die Objekte einfach gehalten, schwarz-weiss mit einzelnen roten Wolken. Bei Inaktivität lösen sich die Wolken wieder auf.

Da die einzelnen Elemente am besten in Gruppen bedient werden, entstehen interessante Dynamiken zwischen den einzelnen Spielern. Sie können kollaborieren, aber auch alle einzeln arbeiten oder sogar jammen und ein unerwartetes Ergebnis erzeugen.

Ziel

Wir wollen fremde Menschen zusammenbringen, indem sie auf einfache Weise kollaborieren und eine gemeinsame visuelle Sprache finden.

Komponenten

Inklings besteht aus einer Konsole (Plastikbox), die den Elektrocontroller (ein Arduino UNO) und zwei Breadboards enthält. Vier Anschlüsse führen zu den physischen Controllern (s.o.), die alle mit einem 5-poligen 2m Kabel mit der Konsole verbunden sind. Die Konsole ist wiederum mit dem Ausgabegerät (z.B. ein iMac) verbunden. Auf dem Ausgabegerät läuft ein Processing-Script, mit dem die Benutzer von Inklings über die Controller interagieren können.

Bau

Wir haben die einzelnen Komponenten in der Werkstatt gebaut oder angepasst. Dauer: ca. 1 1/2 Tage

  • Stab: Wir haben ein Plastikrohr zugeschnitten, und einen passenden Plexiglas-Deckel dazu gelasert. Dann haben wir den Beschleunigungssensor im oberen Ende des Rohrs befestigt.
  • Box: Die Box hat nur noch ein Loch für das Kabel gebraucht, danach haben wir den Piezo-Sensor eingesetzt.
  • Zylinder: Den Zylinder haben wir aus zwei Holzquadern hergestellt. An der Drehbank haben wir sie in Zylinderform gebracht und anschliessend mit dem Bohrer die Löcher für das Kabel, das Potentiometer und eine Plexiglasabdeckung (mit dem Lasercutter geschnitten) gebohrt. Am Ende haben wir den Potentiometer mit Heissleim und Komponentenkleber an den beiden Zylindern befestigt.
  • Ball: Den Ball wir nur ein kleines bisschen aufschneiden um den Flex-Sensor einzufügen.

Lackieren

Wir haben die Teile an zwei Tagen in ca. 6 Schritten schwarz lackiert (den Ball rot) und einen “Inklings” Schriftzug mit einer Kartonschablone aufgesprayt.

Abschluss

Zum Schluss haben wir ein USB Kabel geteilt, um es durch das Loch in der Box zu bringen.

Coding

Um unsere Tintenwolken zu kreieren, sind wir von einer Partikelklasse aus dem Buch “Generative Gestaltung” ausgegangen, die wir erweitert und unseren Bedürfnissen angepasst haben. So nimmt jeder der Controller Einfluss auf die Partikel und deren Muster. Die Partikel haben wir mit einer eine Lebenszeit versehen, die von den Nutzern mitbestimmt werden kann.

Am meisten Mühe bereitetes es uns, den Stab so einzusetzen, dass wir die (bei 180° wieder absteigenden) Werte sinnvoll einsetzen konnten. Im Verlaufe des Programmierens haben wir einen zweiten Modus erarbeitet, der durch schnelles Herunterziehen des Stabs aktiviert wird.

Es folgen der Arduino- und der Screen-Code:

[java collapse="true" title="Arduino Code"]
#include <Wire.h> //Include the Wire library
#include <MMA_7455.h> //Include the MMA_7455 library

#define SHAKE A0
#define TWIST A1
#define SQUEEZE A2

MMA_7455 mySensor = MMA_7455(); //Make an instance of MMA_7455

char xVal, yVal, zVal; //Variables for the values from the sensor

void setup()
{
Serial.begin(9600);
// Set the sensitivity you want to use
// 2 = 2g, 4 = 4g, 8 = 8g
mySensor.initSensitivity(2);
// Calibrate the Offset, that values corespond in
// flat position to: xVal = -30, yVal = -20, zVal = +20
// !!!Activate this after having the first values read out!!!
//mySensor.calibrateOffset(5, 20, -68);
}

void loop()
{
int ShakeVal = analogRead(SHAKE);
int TwistVal = analogRead(TWIST);
int SqueezeVal = analogRead(SQUEEZE);

xVal = mySensor.readAxis(‘x’); //Read out the ‘x’ Axis
yVal = mySensor.readAxis(‘y’); //Read out the ‘y’ Axis
zVal = mySensor.readAxis(‘z’); //Read out the ‘z’ Axis

Serial.print(ShakeVal);
Serial.print(",");
Serial.print(TwistVal);
Serial.print(",");
Serial.print(SqueezeVal);
Serial.print(",");
Serial.print(xVal, DEC);
Serial.print(",");
Serial.print(yVal, DEC);
Serial.print(",");
Serial.print(zVal, DEC);
Serial.println(",");
}
[/java]

[java collapse="true" title="Screen Code"]
import processing.opengl.*;
import processing.serial.*;

int sizeX = 1680;
int sizeY = 1050;

int maxAgents = 60000;

int mode = 0; // Mode 0: Normal clouds, Mode 1: Hairlines

// —— agents ——
ArrayList agents = new ArrayList();
int agentsCount = 0;
float noiseScale = 50, noiseStrength = 10, noiseZRange = 0.4;
float overlayAlpha = 10, agentsAlpha = 10, strokeWidth = 0.3;

color strokeColor = color(255, 255, 255);

Serial myPort;

float shakeVal, twistVal, squeezeVal, swingValX, swingValY, swingValZ, swingValXOld, swingValYOld, swingValZOld;
int shakeValMapped;

float oldValue = 0.995;
float newValue = 0.005;

int coords[] = new int[6];

float oldSquezeVal = 0;

long previousTime;
long currentTime;
int breakTime = 500;

boolean squeezePressed = false;

boolean swingActive = false;

boolean logo = true;

PImage img;

void setup ()
{

size(sizeX, sizeY, OPENGL);

background(0);
smooth();

//for(int i=0; i<agents.size(); i++) agents.add(new Agent());

//println(Serial.list());
myPort = new Serial(this, Serial.list()[0], 9600);

//cam = new Capture(this, width, height);

img = loadImage("logo.png");

}

void draw ()
{

noCursor();

if(logo == true) {
background(img);
}

int newAgents = 0;

fill(0, overlayAlpha);
noStroke();
rect(0,0,width,height);

//agentsCount = shakeVal*15;

//println(squeezeVal);
//println(oldSquezeVal);

//println(shakeVal);

// println(agents.size());

//strokeWidth = map(abs(swingValZ), 0, 128, 0.01, 0.5);

if(mode == 0) noiseStrength = map(abs(swingValX), 0, 75, 10, 15);
else if(mode == 1) noiseStrength = map(twistVal, 0, 1023, 10, 15);
//println(noiseStrength);
noiseScale = map(twistVal, 0, 1023, 20, 150);

currentTime = millis();

int squeezeTrigger = 15;

if(squeezeVal > 0 && (squeezeVal < 80 || squeezeVal > 110))
{
strokeColor = color(255, 0, 0);
squeezePressed = true;
if(squeezeVal < 80) newAgents += map(squeezeVal, 0, 100, maxAgents / 200, 0);
else if(squeezeVal > 110) newAgents += map(squeezeVal, 200, 100, maxAgents / 200, 0);
} else
{
strokeColor = color(255, 255, 255);
squeezePressed = false;
}

// println(oldSquezeVal + " " + squeezeVal + " " + strokeColor + " " + squeezePressed);

if ( currentTime – previousTime > breakTime )
{
previousTime = currentTime;
}

//rotate(radians(swingValX));

newAgents += int(map(shakeVal, 0, 1023, 0, maxAgents / 10));

//newAgents = 100;

//println(agents.size());

if(agents.size() < maxAgents) {

for(int i = 0; i < newAgents; i++) {

agents.add(new Agent(strokeColor, agentsAlpha, agents.size()));

}

}

for(int i=0; i<agents.size(); i++) {

Agent currentAgent = (Agent) agents.get(i);

if(currentAgent.isAlive())
{
currentAgent.update();
}else
{
agents.remove(i);
}
}

//println(agentsCount);
//println(shakeVal);

// print("\t");
// print(twistVal);
// print("\t");
// print(squeezeVal);
// print("\t");
// print(swingValX);
// print("\t");
// print(swingValY);
// print("\t");
// print(swingValZ);
// print("\t");
// print(swingValXOld);
// print("\t");
// print(swingValYOld);
// print("\t");
// print(swingValZOld);
// println();

if(abs(swingValZ) > 110 && swingActive == false) {

if(mode == 0) mode = 1;
else mode = 0;

} else if(swingActive == true && swingValZ < 110) {

swingActive = false;

}

}

void keyPressed() {

if(key == ‘s’) {

/*if (cam.available ()) {
cam.read();
cam.save("cam.png");
}*/
save("screen.png");

} else if (key == ‘m’) {

if(mode == 0) mode = 1;
else mode = 0;

} else if (key == ‘b’) {

if(logo == false) logo = true;
else logo = false;

}

}

void serialEvent(Serial myPort) // Is called everytime there is new data to read
{
if (myPort.available() > 0)
{
String completeString = myPort.readStringUntil(10); // Read the Serial port until there is a linefeed/carriage return
if (completeString != null) // If there is valid data insode the String
{
trim(completeString); // Remove whitespace characters at the beginning and end of the string
String separateValues[] = split(completeString, ","); // Split the string everytime a delimiter is received

//println("—-" + separateValues.length + "—-");

for(int j = 0; j < separateValues.length; j++) {

if(j < 6) coords[j] = int(separateValues[j]);
//println(j);

}

if(separateValues.length == 7) {

shakeVal = coords[0];
twistVal = coords[1];
squeezeVal = coords[2];
swingValX = coords[3];
swingValY = coords[4];
swingValZ = coords[5];

// if(swingValXOld != 0) swingValX = swingValX * newValue + swingValXOld * oldValue;
// if(swingValYOld != 0) swingValY = swingValY * newValue + swingValYOld * oldValue;
// // if(swingValZOld != 0) swingValZ = swingValZ * newValue + swingValZOld * oldValue;

// swingValXOld = swingValX;
// swingValYOld = swingValY;
// // swingValZOld = swingValZ;

}

}
}
}

boolean sketchFullScreen() {
return true;
}
[/java]

[java collapse="true" title="Screen Code – Agent Klasse"]
class Agent
{

PVector p, pOld;
float noiseZ, noiseZVelocity = 0.01;
float stepSize, angle;
long birthTime;
long lifeTime = 2000;
color strokeColor;
float agentAlpha;

Agent (color strokeColorInput, float agentAlphaInput, int agentsSize)
{
p = new PVector(random(width),random(height));
pOld = new PVector(p.x,p.y);
stepSize = random(1,10);
// init noiseZ
setNoiseZRange(noiseZRange);
birthTime = millis();
strokeColor = strokeColorInput;
agentAlpha = agentAlphaInput;
lifeTime = round(map(agentsSize, 0, maxAgents, 1000, 10000));

// println(lifeTime);
}

void update ()
{

if(mode == 0)
{

//angle = noise(p.x/noiseScale + map(abs(swingValX), 0, 75, 2, 6), p.y/noiseScale + map(abs(swingValY), 0, 75, 2, 6), noiseZ) * noiseStrength;
//angle = noise(p.x/noiseScale, p.y/noiseScale, noiseZ + map(abs(swingValZ), 0, 128, 2, 12)) * noiseStrength;

//stepSize = map(abs(swingValZ), 0, 128, 1, 10);

angle = noise(p.x/noiseScale, p.y/noiseScale, noiseZ) * noiseStrength;

p.x += cos(angle) * stepSize;
p.y += sin(angle) * stepSize;

} else if(mode == 1)
{

float angleX = (p.x/noiseScale + swingValX/noiseScale);
float angleY = (p.y/noiseScale + swingValY/noiseScale);

p.x += cos(angleX) * stepSize;
p.y += sin(angleY) * stepSize;

}

// offscreen wrap
if (p.x<-10) p.x=pOld.x=width+10;
if (p.x>width+10) p.x=pOld.x=-10;
if (p.y<-10) p.y=pOld.y=height+10;
if (p.y>height+10) p.y=pOld.y=-10;

stroke(strokeColor, agentAlpha);
strokeWeight(strokeWidth*stepSize);
line(pOld.x,pOld.y, p.x,p.y);

pOld.set(p);
noiseZ += noiseZVelocity;
}

boolean isAlive() {

if(millis() – birthTime < lifeTime)
{

return true;

}

return false;

}

void setNoiseZRange ( float theNoiseZRange )
{
// small values will increase grouping of the agents
noiseZ = random(theNoiseZRange);
}
}
[/java]

Nils misst die maximalgrösse eines Arrays

Play

Zum Abschluss haben ein paar Mitstudenten die Controller in die Hand genommen und inklings auf Herz und Nieren getestet. Das Ergebnis ist in den folgenden Bildern und im Video dokumentiert: