In diesem Kapitel wird gezeigt, wie man über das TUIO-Protokoll Multitouch-Signale verarbeiten kann.

TUIO ist ein Protokoll, das es ermöglich, über ein Internetprotokoll zwischen Eingabegerät (z.B. Android-Tablet oder iPad) und Anwendungsrechner zu kommunizieren. Bei dieser Möglichkeit lässt sich keine direkte Manipulation realisieren, die Ausgabe geschieht auf einem anderen Gerät als die Eingabe.

Wie in der Abbildung ersichtlich, nutzt TUIO das OSC-Internetprotokoll, welches wiederum auf UDP aufsetzt. Daher zunächst ein paar Worte zu OSC.

2.1 OSC

OSC steht für Open Sound Control und ist "protocol for communication among computers, sound synthesizers, and other multimedia devices that is optimized for modern networking technology and has been used in many application areas." (von der OSC-Webseite). OSC wurde an der Universität UC Berkeley entwickelt.

Das Protokoll schickt Daten in folgender allgemeiner Form:

/identifier DATEN

In Processing kann man mit Hilfe der Library oscP5 solche Nachrichten empfangen. Um eine Library zu installieren, muss man lediglich unter Sketch > Import Library... > Add Library... nach dem Namen der Library suchen und kann diese dann direkt runterladen und installieren lassen.

Die Beispielprogramme oscP5parsing und oscP5plug stellen verschiedene Möglichkeiten zur Verfügung, die OSC-Nachrichten abzufangen.

2.1.1 TouchOSC

Wer OSC ausprobieren möchte, kann die App TouchOSC für Android oder iOS installieren (kostenpflichtig). Dann muss nur der Zielhost (d.h. der Rechner, auf dem Processing läuft) angegeben werden. TouchOSC zeigt eine Oberfläche mit verschiedenen Schalteelementen, die z.B. so aussehen:

Ihr Tablet verschickt dann Nachrichten der folgenden Form an den Rechner bzw. Processing:

/1/fader1 0.533
/1/fader2 0.20084

2.1.2 oscP5plug

Wenn Sie oscP5plug verwenden, um die obigen Nachrichten zu empfangen, müssen Sie zunächst in setup() neue Methoden anmelden, die die Nachrichten für Fader 1 und 2 empfangen sollen:

oscP5 = new OscP5(this, 8000);
oscP5.plug(this, "fader1", "/1/fader1");
oscP5.plug(this, "fader2", "/1/fader2");

Außderm definieren Sie die Methoden mit der entsprechenden Anzahl der erwarteten Parameter:

void fader1(float val) {
    println("# fader1: " + val);
}

void fader2(float val) {
    println("# fader2: " + val);
}

Diese Methoden werden dann entsprechend aufgerufen, sobald eine OSC-Nachricht mit diesen Headern auftaucht.

Typischerweise speichert man die Werte in Variablen (oder Array, Liste ...) und verwendet sie dann in der draw()-Methode.

2.2 TUIO

TUIO wurde von Martin Kaltenbrunner entwickelt, um interaktive Systeme wie den reacTable und den tDesk zu betreiben. Diese Systeme beschäftigten sich nicht nur mit Multitouch, sondern auch mit physischen Gegenständen als Eingaberegler, also mit Tangible User Interafaces.

Wir konzentrieren uns jedoch auf die Multitouch-Aspekte des Protokolls.

TUIO ist ein Protokoll, so dass ein beliebiger Computer in einer (fast) beliebigen Sprache die Signale eines Multitouch-fähigen Geräts empfangen und verarbeiten kann. Das touch-fähige Gerät ist dann die Eingabeeinheit, der Computer kann diese Verarbeiten, indem er den User z.B. grafische Objekte am Computerbildschirm manipulieren lässt.

Der Computer fungiert als Client und benötigt eine Software zum Empfang. Auf der TUIO-Webseite findet man viele fertige TUIO-Clients für fast alle gängigen Programmiersprachen. Wir verwenden Processing und zeigen weiter unten, wie man den Processing-Client verwendet.

TUIO verwendet OSC, um seine Nachrichten zu verschicken. Wenn man sich die Nachrichten ansieht, erkennt man das OSC-Format:

/tuio/2Dcur source application@address
/tuio/2Dcur alive s_id0 ... s_idN
/tuio/2Dcur set s_id x_pos y_pos x_vel y_vel m_accel
/tuio/2Dcur fseq f_id

2.2.1 TUIO Processing

Laden Sie das Paket TUIO_Processing.zip von der TUIO-Software-Seite, entpacken Sie es und schieben Sie es in Ihr Sketchbook-Verzeichnis.

Wichtig: Benennen Sie das Verzeichnis TUIO_Processing in TUIO um. Dann starten Sie Processing erneut.

Unter File > Examples... sollten Sie jetzt im Baum unter Contributed Libraries den Eintrag TUIO finden und dort TuioDemo laden.

Fehlerkorrektur: Sie müssen die folgende Zeile ganz oben in TuioDemo hinzufügen:

import java.util.*;

Bevor Sie dies Testen können, müssen Sie noch einen TUIO-Sever aktivieren. Dies kann ein Android-Gerät (Smartphone, Tablet) oder ein iOS-Gerärt (iPhone, iPad, iPod Touch) sein. Wie Sie die App bedienen, steht im nächsten Abschnitt.

Wenn Sie sowohl TuioDemo als auch TuioDroid/TuioPad am Laufen haben, sollten Sie die Eingabe am Endgerät auf Ihrem Processing-Rechner sehen:

2.2.2 TuioDroid und TuioPad

Für Android-Geräte laden Sie TuioDroid bei Google Play. Für iOS-Geräte laden Sie TuioPad aus dem AppStore. Beides ist kostenlos. Sobald Sie die App starten, sehen Sie den Einstellungsbildschirm (zu dem Sie zurückkommen, wenn Sie das Gerät schütteln):

Hier müssen Sie die Host-Adresse im Feld host (oder server) eingeben - das ist die IP-Adresse des Rechners, auf dem Processing läuft. Auf dem Mac finden Sie die z.B. in Systemeinstellungen unter Netzwerk.

Sobald Sie auf Start drücken, sind Sie im Eingabemodus und können mit den Fingern über die Oberfläche fahren. Die App schickt die Daten direkt an den Rechner, auf dem der TUIO-Client läuft.

2.2.3 Eigener TUIO-Client

Wenn Sie sich TuioDemo ansehen, sehen Sie folgende Komponenten. Zunächst wird eine TUIO-Library importiert, die eine reine Java-Library ist.

import TUIO.*;

TuioProcessing tuioClient;

In setup() wird tuioClient instanziiert:

tuioClient  = new TuioProcessing(this);

Sobald das getan ist, können Sie sogenannte Callback-Funktionen einrichten, die immer dann aufgerufen werden, wenn das entsprechende Ereignis passiert, ähnlich wie keyPressed() und mousePressed(). Wir betrachten nur die "Cursor"-Funktionen, da diese für Multitouch relevant sind. Sie sehen auch gleich, welche Informationen im TuioCursor-Objekt untergebracht sind:

// called when a cursor is added to the scene

void addTuioCursor(TuioCursor tcur) {
  println("add cursor " + tcur.getCursorID()
          + " (" + tcur.getSessionID() + ") "
          + tcur.getX() + " " + tcur.getY());
}

// called when a cursor is moved

void updateTuioCursor (TuioCursor tcur) {
  println("update cursor " + tcur.getCursorID()
          + " (" + tcur.getSessionID()+ ") "
          + tcur.getX() + " " + tcur.getY()
          + " " + tcur.getMotionSpeed()
          + " " + tcur.getMotionAccel());
}

// called when a cursor is removed from the scene

void removeTuioCursor(TuioCursor tcur) {
  println("remove cursor " + tcur.getCursorID()
          + " (" +tcur.getSessionID() + ")");
}

Jeder Cursor hat also unter anderem:

Der Unterschied zwischen ID und Session-ID ist folgender: Sobald ein Finger auf den Bildschirm kommt, bekommt er einen ID, der sich von allen anderen aktuell auf dem Bildschirm befindlichen IDs unterscheidet. Sind z.B. schon drei Finger auf dem Bildschirm mit IDs 0, 1 und 2, dann bekommt der neue Finger i.d.R. den ID 3. Der Session-ID hingegen wird so ausgesucht, dass der ID über die Zeit hinweg nur einmalig vorkommt: Wenn schon 22-mal interagiert wurde, bekommt der nächste Finger den Session-ID 23.

Mehr Detail finden Sie in der TUIO-Spezifikation.

Noch wichtiger: die TUIO-Java-API. Hier finden Sie die API für Klassen wie TuioCursor.

Der Pfad eines TuioCursor (Methode getPath) enthält einen Vektor mit allen bisherigen Punkten. Der letzte Punkt des Vektors ist der aktuelle.

In TuioDemo wird kein Gebrauch von den Callback-Funktionen gemacht. Stattdessen wird die Liste aller Touchpunkte verwendet, die in jedem Frame zur Verfügung steht:

Vector tuioObjectList = tuioClient.getTuioObjects();
for (int i = 0; i < tuioObjectList.size(); i++) {
     TuioObject tobj = (TuioObject)tuioObjectList.elementAt(i);
     stroke(0);
     fill(0);
     pushMatrix();
     translate(tobj.getScreenX(width),
               tobj.getScreenY(height));
     rotate(tobj.getAngle());
     rect(-obj_size/2, -obj_size/2, obj_size, obj_size);
     popMatrix();
     fill(255);
     text("" + tobj.getSymbolID(),
          tobj.getScreenX(width), tobj.getScreenY(height));
}

Wichiger Hinweis hier: Um die echten Screen-Koordinaten eines TuioCursors zu bekommen, bedient man sicher der Methoden getScreenX und getScreenY, zusammen mit width/height des Fensters.

2.3 Interaktive Elemente und TUIO

Wir verknüpfen jetzt unsere interaktiven Elemente zum Verschieben und Drehen mit Multitouch per TUIO:

Dazu verwenden wir die Klasse InteracitveThing. Dazu kommt das folgende Hauptprogramm:

import TUIO.*;
import java.util.*;

int TOUCH_POINT_DIAMETER = 14;
TuioProcessing tuioClient;
List things =
        new ArrayList();

void setup() {
  tuioClient  = new TuioProcessing(this);
  size(300, 300);
  smooth();
  textAlign(CENTER);
  textSize(TOUCH_POINT_DIAMETER-4);
  ellipseMode(CENTER);

  things.add(new InteractiveRect(50, 80, 100, 120));
  things.add(new InteractiveRect(180, 50, 70, 50));
}

void draw() {
  background(100);

  // Alle objekte zeichnen
  for (InteractiveThing thing: things) {
    thing.draw();
  }

  drawMTCursors();
}

// draws multitouch cursors and id's based on
// the TUIO cursor vector
void drawMTCursors() {
  Iterator it = tuioClient.getTuioCursors().iterator();
  while (it.hasNext ()) {
    TuioCursor tcur = (TuioCursor)it.next();
    int id = tcur.getCursorID();
    int x = tcur.getPosition().getScreenX(width);
    int y = tcur.getPosition().getScreenY(height);

    stroke(#54C426);
    fill(#54C426, 50); // green
    ellipse(x, y, TOUCH_POINT_DIAMETER, TOUCH_POINT_DIAMETER);
    fill(#35A0F0); // black
    text(id, x + TOUCH_POINT_DIAMETER/2+4,
    y - TOUCH_POINT_DIAMETER/2+4);
  }
}

// these callback methods are called whenever a TUIO event occurs

// called when an object is added to the scene
void addTuioObject(TuioObject tcur) {
}

// called when an object is removed from the scene
void removeTuioObject(TuioObject tobj) {
}

// called when an object is moved
void updateTuioObject (TuioObject tcur) {
}

// called when a cursor is added to the scene
void addTuioCursor(TuioCursor tcur) {
}

// called when a cursor is moved
void updateTuioCursor (TuioCursor tcur) {
  Vector path = tcur.getPath();
  if (path.size() > 1) {
    TuioPoint current = path.get(path.size()-1);
    TuioPoint prev = path.get(path.size()-2);
    for (InteractiveThing thing: things) {      
      thing.update(prev.getScreenX(width),
      prev.getScreenY(height),
      current.getScreenX(width),
      current.getScreenY(height));
    }
  }
}

// called when a cursor is removed from the scene
void removeTuioCursor(TuioCursor tcur) {
}

// called after each message bundle
// representing the end of an image frame
void refresh(TuioTime bundleTime) {
}

Der entscheidende Teil ist die Update-Methode, die jedesmal aufgerufen wird, wenn ein Finger ein Stück wandert. Wir suchen uns aus dem Pfad, der an diesem Cursor hängt, die letzten zwei Punkte raus und verwenden diese für unser update().

// called when a cursor is moved
void updateTuioCursor (TuioCursor tcur) {
  Vector path = tcur.getPath();
  if (path.size() > 1) {
    TuioPoint current = path.get(path.size()-1);
    TuioPoint prev = path.get(path.size()-2);
    for (InteractiveThing thing: things) {      
      thing.update(prev.getScreenX(width),
      prev.getScreenY(height),
      current.getScreenX(width),
      current.getScreenY(height));
    }
  }
}

Damit haben wir interaktive Schaltflächen für alle TUIO-kompatiblen Geräte erschaffen.