Spotted

Aus toolbox_interaktion
Wechseln zu: Navigation, Suche
BME5 spotted interactive projection.jpg

„Spotted" ist eine Projektarbeit, die im Rahmen des Studiengangs Media Engineering an der Technischen Hochschule Nürnberg Georg Simon Ohm innerhalb des 5. und 6. Semesters von einer Projektgruppe mit sechs Personen entwickelt wird. Wie der Name „Spotted“ bereits vermuten lässt, geht es darum, dass Personen erkannt („to spot“) und ihre Bewegungen nachverfolgt (tracking) werden. Daneben liegt die englische Übersetzung von „gepunktet“ = spotted sehr nahe, da die visuellen Ergebnisse der Projektarbeit einem Muster aus Punkten gleicht.

Genauer gesagt handelt es sich bei diesem Projekt um eine interaktive Projektion, bei der viele Personen interagieren können, aus visuellen und akustischen Effekten. Das Projekt an sich wird im Atrium des BB Gebäudes umgesetzt und im Herbst 2015 an der Langen Nacht der Wissenschaft präsentiert.

Konzept

Projektionsfläche im BB-Gebäude
Interagierende Kreisschwärme

Die Projektion besteht aus insgesamt 3 Programmteilen, die ineinander übergreifen. Betritt eine neue Person den Projektionsbereich wird um diese Person einen Anziehungsradius gezeichnet, dem ein Farbe zugeordnet wird. Einfache geometrische Formen fallen in der gleichen Farbe von den Treppenfassaden (Physics 1) und werden sobald diese den Boden berühren vom Anziehungsradius der Person angezogen (Physics 2). Es entstehen somit Objektschwärme in verschiedenen Farbgebungen. Beim Annähern von zwei Personen fangen die Kreise an zu interagieren. Dabei wird ein Blinkeffekt der Kreisschwärme ausgelöst. Des Weiteren sind Blinkeffekte an den Treppenfassaden geplant, beispielsweise wenn eine Person das Projektionsfeld betritt oder verlässt. In Projektabschnitt 2 wurden mehrere Modi zusaetzlich implementiert: Darunter ein Interaktionsmodus in dem mit Ausstellungsobjekten gespielt werden kann und sie die Farbe des Trackingobjekts des Besuchers annehmen, sobald er sich dem Objekt naehert. Daneben gibt es noch einen Verdraengungsmodus in dem man durch ein Feld aus Baellen wandern kann und sie in einem gewissen Radius abstoesst. Folgende Abbildungen illustrieren dies deutlicher.

Interagierendes Installationsobjekt

Eine Zusammenarbeit mit der Fachschaft Architektur war in diesem Zusammenhang angedacht, konnte aber nicht naeher spezifiziert werden.

Wasserfallmodus
Leerlaufmodus


Versionsverwaltung / Git

Das System der Versionsverwaltung wird zur Erfassung von Änderungen an Dokumenten oder Dateien verwendet. Alle Versionen werden in einem Archiv mit Zeitstempel und Benutzerkennung gesichert und können später wiederhergestellt werden. Versionsverwaltungssysteme werden typischerweise in der Softwareentwicklung eingesetzt, um Quelltexte zu verwalten. Ein wichtiger Teil des Versionsverwaltungssystems ist ein Programm, das in der Lage ist, diese Arbeitskopie mit den Daten des Repositorys (Verzeichnis zur Speicherung und Verwaltung des Codes) zu synchronisieren. Das Übertragen einer Version aus dem Repository in die Arbeitskopie wird als Checkout, Auschecken oder Aktualisieren bezeichnet, während die umgekehrte Übertragung Check-in, Einchecken oder Commit genannt wird. Solche Programme werden entweder kommandozeilenorientiert, mit grafischer Benutzeroberfläche oder als Plugin für integrierte Softwareentwicklungsumgebungen ausgeführt. Häufig werden mehrere dieser verschiedenen Zugriffsmöglichkeiten wahlweise bereitgestellt. Git ist eine freie Software zur verteilten Versionsverwaltung von Dateien, die ursprünglich für die Quellcode-Verwaltung des Linux-Kernels entwickelt wurde. Sowohl das Erstellen neuer Entwicklungszweige (branching), als auch das Verschmelzen zweier oder mehrerer Zweige (merging) sind integraler Bestandteil der Arbeit mit Git und fest in die Git-Werkzeuge eingebaut. Allerdings traten schon nach kurzer Zeit Probleme auf, zu deren Lösung fundierte Kenntnisse in den Bereichen Kommandozeilen-Scripting und Git nötig gewesen wären. Wir sahen uns also nach Alternativen um und beschlossen für die zukünftige Bedienung von Git eine grafische Benutzeroberfläche zu benutzen, welche es uns leichter machte, den Verwaltungsprozess nachzuvollziehen. Hierzu schien uns die Software Sourcetree am geeignetsten. Sie zeigt die wichtigsten Befehle in einer einfachen Oberfläche an, sodass für diese Funktionen eine Bedienung von der Kommandozeile aus nicht notwendig ist. Außerdem war sie plattformübergreifend ausführbar, sodass sowohl die Mac Nutzer als auch diejenigen, die mit Microsoft arbeiteten, davon profitieren konnten. Als Plattform für unseren Code entschieden wir uns gemeinsam für „bitbucket.org“. Ein zentrales Teamkonto vereinfachte uns hier die Verwaltung der Repositories.

SourceTree.png

XML

XML ist „eine Auszeichnungssprache zur Darstellung hierarchisch strukturierter Daten in Form von Textdateien“. Sie sind für den Menschen schnell und leicht verständlich zu lesen, es ist sofort erkenntlich welcher Wert zur bestimmten Bezeichnung gehört und wo man eingreifen muss um diesen zu verändern. Ein XML ist immer nach dem gleichen Prinzip aufgebaut, wie man an der Abbildung erkennen kann.

XML1.png

Man sieht sofort die grundlegende hierarchische Struktur von XML, die wie eine Baumstruktur verläuft. Ein passendes Paar <’Bezeichnung’> und </’Bezeichnung’> erstellt ein Element, beziehungsweise schafft eine neue Ebene. Auf der oberen Dokumentenebene muss sich mindestens ein Element befinden. Im zweiten Projektabschnitt wurde die Nutzung von XML ausgeweitet, es konnte Mapping gespeichert und geladen werden, mehrere Ausstellungsobjekte sowie alle noetigen IP Adressen einer Fernsteuerungsanbindung. Folgende Beispiele sind nur grundsaetzliche Implementierungen von XML, letzten Endes war der Umfang der Nutzung deutlich komplexer.

Der Tracker

In unserer Tracking-Software gibt es auf der ersten Ebene vier Elemente: „ip“, „cam“, „inputSize“, „contourFinder“. In jedem dieser Elemente gibt es auch mindestens ein weiteres „Unterelement“ die wieder mit einem passenden Paar von <’...’> und </’...’> erstellt werden. Viele unserer Variablen muss man je nach Verwendung der Installation und der gewählten Räumlichkeit nachträglich anpassen und justieren. Damit wir nicht bei jeder kleinen Änderung in den „Hardcode“ der Programme eingreifen müssen, haben wir versucht diese in verschiedene XML-Files auszulagern. Das Open Frameworks Addon „ofxXmlSettings“ bietet eine sehr komfortable Möglichkeit dies zutun. Im Folgenden wird anhand unserer Tracking-Software die genaue Vorgehensweise des Addons kurz verdeutlicht.

Auszug aus "ofApp.h" der Tracking Software
"loadSetting" - Methode der Tracking Software


Durch die Funktion „xml.load(xmlFile)“ wird auf mein XML-File „ctosc.xml“ zugegriffen. „xml.“ steht hier für den Verweis auf das Addon „ofxXmlSettings“, dieser Name ist frei wählbar. Zur besseren Verständlichkeit sind wir aber bei dem Kürzel geblieben. „load(xmlFile)“ führt die Funktion des Addons auf die das XML-File in das Programm läd. Der sich in der Klammer befindende String „xmlFile“ verweist auf eine Definition „FILE“ – die das XML darstellt. Ab hier ist das XML-File geladen und bekannt und man kann auf alle darin gespeicherten Elemente zugreifen.

Die Variablen werden ganz normal im Header initialisiert. Anstelle fester Werte wird ihnen aber die Abruffunktion „getValue“ des XML-Addons mitgegeben. Der Aufbau dieser Funktion läuft folgender Maßen ab:

TrackerXML2.png

„getValue“ greift auf die Funktion des Addons zu, die für das Auslesen von Werten zuständig ist. Man gibt hier grundlegend immer 2 Werte mit. Der erste Wert ist ein String und beschreibt die Position des Elements aus dem XML-File, auf das man zugreifen möchte. Hier „contourFinder:maxDistance“ – die erste Wortkette ist die oberste Ebene in dem sich das Zielelement befindet. Ist das Zielelement selbst auf der obersten Ebene des XMLs genügt dieser Eintrag. Bei meinem Beispiel liegt das gewünschte Element aber auf der 2. Ebene, diese Ebene erreicht man durch das Sonderzeichen „:“. Im Anschluss nennt man jetzt die Bezeichnung des Zielelements oder der nächsten Ebene. Bei diesem Beispiel ist es aber mein Zielelement „maxDistance“. Der zweite Wert ist ein Integer – dieser gibt einen Standardwert an, sollte das Element nicht gefunden werden oder das XML-File nicht geladen werden können wird dieser Wert verwendet. Dies wird mit allen wichtigen und in der XML abgespeicherten Werte für Variablen der Software gemacht. Die „if“- und „else if“-Bedingung in der „loadSettings“-Methode bezieht sich auf die gewählte „deviceID“ bzw. Kamera-ID und wählt je nach verwendeter Kamera die passende Auflösung in Pixeln aus. Der Wert 1 steht hier für die Infrarot-Kamera und „0“ steht für die Frontalkamera meines Macbooks.

Das „ofxXmlSettings“-Addon bietet nicht nur die Möglichkeit Werte auszulesen, man kann auch ganze XML-Files erstellen oder die Werte nach Veränderung beim Programmablauf neu abspeichern. Bei unserem Tracker habe ich diese Funktion genutzt, da wir Werte wie zum Beispiel „Threshold“ „minDistance“ und „maxDistance“ je nach Räumlichkeit und verwendeter Kamera-Hardware live neujustieren müssen.

TrackerXML3.png

Man nutzt hier anstelle der „getValue“ die Funktion „setValue“. Der String definiert allerdings nur die Beschreibung der Position, nicht aber den aktuellen Wert, des gesuchten Elements im XML. An der Stelle an dem vorher der Standardwert mitgegeben wurde, gibt man jetzt die zu speichernde Variable mit. Mit der Funktion „xml.saveFile(FILE); schließt man den Speichervorgang ab.

PhysicsEngine

Wie auch in unserem Tracker, befinden sich in unserer Physics-Engine-Software wichtige Variablen, die man ganz nach Verwendung der Engine ändern und anpassen muss. Um den Überblick noch ein wenig leichter zugestellten haben wir hier aber gleich verschiedene XML-Files erstellt. Wegen der dadurch entstandenen Vielzahl an Methoden, für das Abrufen der einzelnen XMLs, haben wir für das einlesen der XMLs eine extra Klasse erstellt.

Header "xml.h" der Physics Engine
Auszug der Klasse "xml.cpp" - Physics Engine


Wie man erkennen kann haben wir für die Physics-Engine am Ende fuenf verschiedene XML-Files erstellt. Das dritte File „COLOR_FILE“ stellt hier einen Sonderfall dar. Als wir uns entschieden hatten die Farben für unsere visuelle Darstellung für jedes Objekt individuell festzulegen, haben wir die Werte für „RGB“ ebenfalls in ein XML ausgelagert. Um diese drei einzelnen Werte die jede Farbe besitzt in unser Programm einzulesen, musste man die Farben in einen Vektor für „ofColor“-Werte speichern. Die Werte des XMLs werden erst in ein ofColor-Element gespeichert und um diese einfacher an die „draw()“-Methode der Engine weiterzugeben wird jedes ofColor-Element in den Vektor gespeichert.

Auszug aus "xml.cpp", "loadColors" -Methode


Da wie erwähnt sämtliche Variablen, die von einem XML-File geladen werden, in eine extra Klasse gespeichert werden, muss man in der „setup()“-Methode unserer Physics-Engine auch die Werte angepasst abrufen. Der Aufruf der Klasse ist auch hier „xml.“ genannt, nicht zu verwechseln mit dem Kürzel in unserem Tracker oder in der XML-Klasse selbst, das direkt auf das Addon verweist.

Auszug "setup()"- Methode der Physics-Enginge


Beispielsweise die Zeile „box2d.setFPS(xml.fps);“. Hier wird nun auf die Variabel „fps“ aus meiner Klasse „xml.cpp“ zugegriffen. Bevor diese Variablen abgerufen werden können ist es aber nötig auch die zwei Methoden „physicsSettings();“ und „loadColors();“ aus der XML-Klasse aufzurufen. Auch hier muss wieder das „xml.“ davorgesetzt werden, damit auf die richtige Klasse zugegriffen wird.

Tracking

Bevor man beginnen kann sich Gedanken darüber zu machen, auf welche Art und Weise man an die Daten der zu trackenden Objekte kommt, muss man sich zu aller erst dafür entscheiden, welche Hardware zur Aufnahme der Kamerabilder verwenden werden soll. Im Folgenden wird auf die verschiedenen Möglichkeiten eingegangen, als auch begründet, warum wir uns jeweils dagegen bzw. dafür entschieden haben.

Wie eingangs bereits angedeutet, gibt es verschieden Möglichkeiten um an das für das Tracking relevante Bildmaterial zu gelangen. Es ist natürlich nahe liegend, die zu verfolgenden Objekte mit einer Filmkamera aufzuzeichnen. Das erste Problem, dass sich dadurch allerdings ergibt besteht darin, dass eine Filmkamera für qualitativ gute Bilder immer ein Mindestmaß an Licht benötigt, da die aufgenommenen Bilder ansonsten zum Großteil schwarz werden würden. Das hätte zur Folge, dass man die zu trackende Fläche künstlich erhellen müsste. Prinzipiell wäre das kein Problem, da wir aber in unserer Installation hauptsächlich mit Projektionen arbeiten, würden wir durch die zusätzliche Lichtquelle unsere Bodenprojektion selbst zerstören. Des Weiteren würde der optische Eindruck erheblich leiden, da Projektionen in einem ansonsten stark abgedunkelten Raum am meisten wirken und beeindrucken können. Ein weiteres Problem bestünde darin, dass eine Filmkamera nicht nur die Personen auf der Bodenfläche aufzeichnen würde, sondern auch das projizierte Bild unter jeder Person. Folglich würden fehlerhafte Daten die visuelle Darstellung negativ beeinflussen, beziehungsweise unter Umständen sogar vollständig zerstören. Um die genannten Probleme zu umgehen, benutzt man für gewöhnlich eine Kombination aus einem Infrarotscheinwerfer und einer Infrarotkamera. Die Vorteile von Infrarot liegen zum einen darin begründet, dass Infrarot auf einer Wellenlänge des Lichts arbeitet, die für das menschliche Auge nicht sichtbar ist. Das bedeutet, dass der Infrarotscheinwerfer, der zum Erhellen der zu trackenden Objekte dient, keinerlei Einfluss auf unsere Bodenprojektion hat. Zusätzlich nimmt eine Infrarotkamera auch nur das auf, was zuvor von einem Infrarotscheinwerfer bestrahlt wurde. Also können wir auch hier ausschließen, dass die Bodenprojektion unsere Aufnahme beeinflusst. Dieses Prinzip wendeten wir bis zum Ende unseres Projektes an.

Infrarotkamera und Infrarotscheinwerfer


Hintergrundsubtraktion

Nachdem nun das Rohmaterial vorliegt, geht es im Folgenden um die Verarbeitung und das Herausarbeiten der relevanten Daten. Um tatsächlich die reine Bewegungsspur eines Objektes zu erfassen, ist es notwendig alle anderen Bildinhalte auszublenden. Dies gelingt durch ein Verfahren mit dem Namen Hintergrundsubtraktion. Nun folgt eine kurze Erläuterung über das Grundprinzip dieses Verfahrens und im Anschluss wird das Ganze, anhand von dem von uns geschriebenen Codes veranschaulicht und erklärt. Die Idee der Hintergrundsubtraktion besteht darin, dass zum Programmstart eine Aufnahme der leeren, zu trackenden Fläche angefertigt wird, welche dann immer als Hintergrund fungiert. Eben jener soll nun automatisch von jedem folgenden Bild abgezogen werden. Was dann übrig bleibt, ist die reine Änderung des aktuellen Bildes zum Hintergrundbild.

Code Beispiel

Der eben beschriebene Ablauf findet in jedem neuen Frame statt, also jedes Mal, wenn ein neues Bild aufgezeichnet wird. Folglich steht diese Funktion in der Update - Funktion meiner Tracking - Klasse.

Update() Methode des Trackers


In jedem neuen Frame (z.69) werden die Aufnahmen meines FrameGrabbers movie in ein ofxCvColorImage namens „colorImage“ übertragen (z.72). Nun folgt die Übertragung von colorImage in ein Graustufenbild mit der Bezeichnung „grayImage“( Datentyp: ofxCvGreyscaleImage, z.75). Dies erfolgt zu dem Zweck, dass sich das Differenzbild dann später leichter nachbearbeiten lässt, um die Bildänderung nochmals deutlicher vom Hintergrund abzugrenzen. Die If-Schleife in Zeile 77 kommt dadurch zustande, dass ich die Aufnahme des Hintergrundbildes, welches vom aktuellen Bild abgezogen werden soll, in einer eigenen Funktion ausgelagert habe, welche über ein Key-Event getriggert wird (z.172).

Key-Event keyPressed() mit Case "Leertaste"


Jedes Mal, wenn der Nutzer die Leertaste betätigt, wird die Funktion „keyPressed()“ aufgerufen und im entsprechenden Case setze ich den Boolean „bLearnBakground“ auf true. Das bedeutet im Umkehrschluss, dass in der Update - Funktion in besagter If-Schleife (z.77) das aktuelle Bild des FrameGrabbers ( „grayImage“) nun in die Variable namens „bg“ (=background), ebenfalls vom Typ Graustufenbild, geschrieben wird. Gleichzeitig wird der boolean „bLearnBakground“ wieder auf false gesetzt (z.79). Nun wende ich die Funktion absDiff() an, welche ein Differenzbild zwischen den beiden Eingangsbilder „bg“ und „grayImage“ berechnet. Dann speichere ich das Ergebnis in dem Grausstufenbild „grayDiff“ (z.83).

links grayImage und rechts grayDiff

Davon legen wir wiederum eine Kopie mit dem Namen „trackedImg“ an. Auf dem „trackedImg“ erkennt man nun nur noch die Bildänderung, allerdings noch etwas undeutlich. Um den Kontrast zu verstärken wird die Funktion „threshold()“ angewendet, (z.87) mit dem Eingangswert „threshold“ (Datentyp int). Diese Funktion vergleicht jeden einzelnen Bildpunkt unseres „trackedImg“ mit dem Differenzwert „threshold“, liegt er darunter so wird der Punkt schwarz, liegt er darüber so wird er weiß. Dadurch erreicht man, dass aus dem Graustufenbild ein reines Schwarz - Weiß - Bild wird. Durch den Gaußschen Weichzeichner z.88) und die Funktion „dilate()“ (z.89) erzeugt man eine homogene Weiße Fläche ohne Löcher. Bevor wir nun die Koordinaten an die Visualisierung weitergeben können, müssen wir sie zunächst berechnen, dies geschieht in der Z.92, in der die Funktion „findContours()“ der Klasse „ContourFinder“ aufgerufen wird.

Addon ofxCv

Der ContourFinder ist eine nützliche Funktionalität die OpenFrameworks standardmäßig mit der Bibliothek OpenCv mit sich bringt. Dennoch haben wir uns für die Verwendung eines Addons, des in OpenFrameworks Kreisen sehr bekannten Programmieres Kyle McDonald entschieden. Es heißt ofxCv und erweitert die Funktionalität von OpenCv durch die Implementierung einiger sehr nützlichen Dinge, wie z.B. der Vergabe von ID’s an erkannte Konturen, als auch deren Persistenzregulierung.

ContourFinder

"trackedImg"

Der ContourFinder wird, wie der Name schon impliziert, dafür verwendet Konturen zu erfassen. In diesem Fall wenden wir die Funktion „findContours()“ auf unser „trackedImg“ an (z.92). Jede gefundene, zusammenhängende Kontur bezeichnet man als „Blob“.


Nun springen wir kurz in die draw() - Methode unserer Klasse, um zu beschreiben, wie nun aus dem trackedImg die Koordinaten extrahiert werden. In der For-Schleife in Zeile 112 wird über alle erfassten Konturen unseres trackedImg iteriert und für jede gefundene Kontur der Mittelpunkt bestimmt (z.113). Dieser Mittelpunkt wird in einem ofPoint „center“ gespeichert und in meinem ContourFinder Objekt gespeichert (z114-116). Abschließend werden die erfassten Daten in einem magenta-farbenen (255,0,255) String ausgegeben. Somit sind wir in der Lage von jeder Person, welche die zu trackende Fläche betritt, die Mittelpunktskoordinaten zu berechnen und ihren Aufenthaltsort eindeutig zu bestimmen. Die hier erfassten Daten werden nun mittels Open Sound Control (OSC) zu einem weiteren Rechner gesendet, welcher sich um die visuelle Komponente unseres Konzeptes kümmert.

Auszug aus der draw() Methode des Trackers


Probleme

Zur Laufzeit veränderbare Variablen des Trackers

Den oben beschriebenen Tracker zu programmieren war an sich nicht besonders schwer oder zeitaufwändig, die Probleme lagen eher im Detail. So gestaltete sich die exakte Positionierung und Kalibrierung der Infrarotkamera als nicht ganz unproblematisch, weil sich immer wieder Probleme bei der Einstellung von Schärfe und Licht ergaben, da die Regulierung der Blende sehr empfindlich ist. Des Weiteren gibt es innerhalb des Trackers eine Vielzahl von Parametern, die es auf die jeweilige Situation optimal einzustellen gilt. Um die Variablen auch zur Laufzeit dynamisch Verändern zu können, habe ich einige Funktionen ergänzt, damit die Einstellungen so genau wie möglich kalibrieren werden können.


• Threshold gibt den Differenzwert an, ob ein Bildpunkt schwarz oder weiß wird.

• MaxDistance: bezeichnet den maximalen Weg den ein Blob pro Frame zurücklegen kann.

• minAreaRadius: bezeichnet die Mindestgröße eines Blobs um erkannt zu werden

• maxAreaRadius: bezeichnet die Maximalgröße eines Blobs

• Persistence bezeichnet die Zeit, wie lange ein Blob und seine ID gespeichert bleibt, nachdem sie verloren wurde.

Verbesserung der Tracking Engine

Da unser Programm, wider Erwarten, bei der Präsentation im ersten Projektabschnitt unter der Last von 10 Leuten, welche gleichzeitig das Spielfeld betraten, zusammen brach, gab es an diesem Punkt enormen Verbesserungsbedarf. Wir haben viel ausprobiert, um die Verarbeitung der OSC - Nachrichten so flüssig und performant wie möglich zu gestalten. Unter Anderem versuchte ich die Anzahl der Nachrichten dadurch zu verringern, dass die erfassten Personenkoordinaten nur dann versendet werden, wenn sie genügend Abweichung zur vorangegangenen Nachricht der selben ID aufweisen (Setzen eines Schwellenwertes). Dies brachte einen kleinen Performance Zuwachs, der aber alleine nicht ausreichen sollte um unser Problem zu beheben. Sehr spät kamen wir auf die Idee, das unser Problem mit den verschiedenen Frame - Raten der einzelnen Teilprogramme zusammenhängen könnte. Nach einigen Testläufen fand ich nun die perfekten Einstellungen. Die Tracking - Engine wurde auf einen Frame - Rate von 15 Frames/Second reduziert, um auch etwaige Performance Einbrüche der Visualisierungs - Komponente auffangen zu können. Nun wurde der Nachrichtenkanal nicht mehr mit Nachrichten überflutet und das Problem des „Laggings“ verschwand.

Des Weiteren habe ich eine Art Testmodus implementiert, welcher es uns erlaubt für Programmiertests Videos einzuspeisen und von den darin enthaltenen Objekten die Koordinaten zu ermitteln. Dies ersparte es uns den Tracking - Aufbau samt Infrarot - Kamera und Scheinwerfer jedes Mal aufs Neue aufbauen zu müssen.

Open Sound Control

Das „Open Sound Control Protocol“ ist ein nachrichtenbasiertes Protokoll zur Kommunikation zwischen Programmen bzw. Computern das hauptsächlich für die Echtzeitverarbeitung von Sound über Netze und Multimedia-Installationen verwendet wird. Steuersignale werden über eine serielle Schnittstelle (zb. USB, Ethernet) oder basierend auf Netzwerkprotokollen (UDP, TCP) versendet um eine Ausgabe zu steuern. OSC ist zum einen sehr schnell, da wir über das WLAN-Netzwerk versenden und zum anderen haben wir kein „Kabelsalat“, da in jedem Computer bereits eine Netzwerk-Schnittstelle integriert ist und somit keine Extra-Geräte nötig sind.

OSC1.png

Eine OSC-Nachricht besteht aus: 1. einer Zeichenkette mit hierarchisch strukturierter Adresse (ähnlich einem Pfad) 2. der Anzahl und Art der Parameter (mit Komma davor) 3. und den einzelnen Werten der Parameter Beispiel einer für unsere Anwendung typische Adresse: /position/id_3 ,iff 3 0.9876 0.1234

Die Nachricht beinhaltet drei Argumente. Zum einen die Kennung (ID) der Personen als int-Wert und die Koordinaten x und y als float-Wert. Allerdings ist hier zu beachten, dass wir die Koordinaten nicht direkt versenden, sondern deren Verhältnis zur Bildschirmgröße, welches wir dann auf Empfängerseite wieder entsprechend auflösen. Wir müssen also noch einen Empfänger implementieren, welcher über OSC die Nachrichten empfängt.

OSC2.png

Das „receiver“-Objekt liest, solange Nachrichten empfangen werden, diese aus und speichert die mitgesendeten Mauskoordinaten in neuen Variablen, wenn sie eine bestimmte Adresse haben („/position/id_“). Die Koordinaten x und y werden wie bereits erwähnt jeweils im Verhältnis zur Bidschirmgröße gesendet, sodass sowohl auf Sender- also auch auf Empfängerseite eine hohe Konformität gewährleistet wird. Hiermit war also der Grundbaustein gesetzt und wir konnten nun von anderen Computern aus unsere Kreisobjekte steuern bzw. bewegen.

An insgesamt 2 weiteren Stellen verwendeten wir das OSC Protokoll: An der Schnittstelle zwischen unserem im zweiten Abschnitt entstandenen Media Interface setzten wir diese Art der Nachrichtenuebertragung ein, aber auch zwischen unserer Fernsteuerung und der PhysicsEngine, was in spaeteren Abschnitten naeher erlaeutert werden soll.

Visuelle Umsetzung

Die Umsetzung unserer visuellen Darstellung erfolgte mit Hilfe von Open Frameworks, einer Bibliothek/Framework für C++ Anwendungen. Dieses stellt dabei geeignete Rahmenbedingungen und etliche vorgefertigte Add-Ons zur Verfügung, die einem das Programmieren von kreativen Projekten erleichtern soll. Die Liste unserer eingebundenen Erweiterungen war am Ende relativ lang und nicht nur auf die Darstellung bezogen. Für diese war aber vor allem “ofxBox2d” Simulations- und “<vector>” programmiertechnisch entscheidend.

Erweiterung im Programmteil "testApp.h"

Mit Hilfe dieser Erweiterung war es uns möglich eine Simulationswelt zu schaffen in der wir z.B. Parameter wie Gravitation in beide Achsenrichtungen oder angestrebte Framerate festlegen konnten.

Initialisierung von Werten im Programmteil "testApp.cpp"


Auch die Anziehungs- (und Abstoßungskraft) unserer einzelnen Elemente konnten wir mit Hilfe dieses Frameworks bestimmen, wie sich in folgender Abbildung erkennen lässt.

Setzen der Anziehungskraft in der Funktion "boid.cpp"


Umstrukturierung des Codes nach MVC Prinzip

Anfang des zweiten Semesters besprachen wir uns im Team und diskutierten die Vor- und Nachteile eines kompletten Neuaufbau des Codes. Wir kamen letztlich zu dem Ergebnis, dass, bevor wir unsere Planungen durchziehen wollten, ein eben solcher notwendig war. Mit Herrn Vincent Walter setzte ich mich so zunächst zusammen, wir analysierten das vorhandene Material, und besprachen eine Strategie für den Neuaufbau. Uns war relativ schnell klar, dass sich eine Programmaufteilung nach dem Muster MVC (Model-View-Controller) für unser Vorhaben am besten eignen würde.

Es beinhaltet:

- Ein Modell, welches alle physikalischen Daten verwaltet und bereit hält

- Einen Controller, welcher sich für den Programmablauf verantwortlich zeigt, das aktualisieren des Models übernimmt und die Benutzereingabe verwaltet.

- Eine Ansichtssteuerung, die sich nur um das Steuern der Darstellung küm- mert, die Daten hierzu werden klassischerweise aus dem Model übertragen.

Wir versprachen uns von diesem Vorhaben mehrere Vorteile. Neben der Performance und der Reduktion der Komplexität unserer größten Klassen hofften wir so, Funktionalitäten besser kapseln und unseren Code logischer ordnen zu können. Auch das Andocken verschiedener Darstellungsmodi erschien uns dadurch einfacher als das einpflegen in das bereits bestehende System.

Mit diesem Umbau des Programmcodes erhielten wir am Schluss ein von der Komplexität deutlich höheres Programm ohne aber in Chaos zu ersticken. Folgende Abbildung soll unseren Fortschritt im zweiten Projektabschnitt verdeutlichen.

Spotted new.png

Unsere Objekte wurden also im Model verwaltet und erstellt und bei Bedarf oder Modiwechsel auch wieder zerstoert.

Implementierung des Tracking Modus

Der Tracking Modus wurde in einer Klassenstruktur verwaltet, die sich "allBoids.cpp" nannte. Dieser sind die Klassen "boid.cpp" und "behaviour.cpp" angeschlossen. Im Prinzip war die Klasse allBoids der Link zu unseren Objekten, die mit der Klasse allBoid erstellt wurden. Jedes dieser Objekte erhielt mit der Klasse behaviour.cpp einen Anweisung wie es sich zu verhalten hatte. Gespeichert wurden die Objekte ueber Zeiger auf die Objekte die in einem Array namens allBoidsArray gespeichert wurden. Das Prinzip ermoeglichte es uns jederzeit performant arbeiten zu koennen, da es die Kopien von Objekten verhinderte.


Besondere Funktionen

Neben der grundsätzlichen Steuerung des Programmablaufs brauchten wir auch kleine Helferfunktionen um unsere Methodik zu unterstützen.

Als großes Problem stelle sich heraus, dass wir aus unserem Tracking Bereich manchmal nicht konsistente bzw. sprunghafte Daten erhielten und die Anziehungspunkte unserer Schwärme somit auch zu schnell die Position wechselten. Um den Bewegungsablauf zu verflüssigen schrieb ich eine Glättungsfunktion die mit rekursiver Mittelung (also unter Bezugnahme auf vorherige Daten) die neu gesetzten Punkte interpolierte und erst dann den Anziehungspunkt aktualisierte. So war ein flüssiger Bewegungsablauf gewährleistet. Diese Art der Bewegungsberechnung wendeten wir auch in anderen Modi unserer Projektion an.

Glättungsfunktion aus Programmteil boid.cpp


Diese Methode nahm zwei float-Werte als Koordinatenpunkte auf und berechnete aus Ihnen sowie der aktuellen Position einen Mittelwert. Die Differenz aus dem aktuellen sowie dem neuen Wert mit einem Gewichtungsfaktor multipliziert, wurde zu dem neuen Wert addiert und der Betrag des Resultats als neue x,y Koordinate an die Set Funktion des Anziehungspunkts weiter gegeben. Diese Funktionalität war insofern ein entscheidender Durchbruch, da man erst mit Hilfe eines flüssigen Bewegungsablaufs der Installation Spass haben konnte. Eine weitere spezielle Funktion unseres Programms war ein Timer, der anfing nach dem Erhalt der letzten Nachricht aus der Tracking-Engine heraus die verstrichene Zeit zu messen und bei Überschreitung eines bestimmten Wertes den Schwarm wieder in die Initialposition zu fahren.

Funktion lifeCycleCheck des Programmteils boid.cpp


Diese Methode gab ein “boolean” zurück und empfing aber auch eins. Wurde “true” in die Funktion gegeben, nahm diese den Zeitpunkt des Aufrufs und nahm so eine neue Messung bzw. veränderte den Referenzwert. Wurde diese mit “false” aufgerufen wurde nur verglichen zwischen dem Zeitpunkt des Aufrufs und der verstrichenen Zeit zum Referenzwert “initTime”. Sobald die Subtraktion des Aufrufzeitpunkts (checkTime) vom Referenzwert (initTime) zu stark von einem Parameter namens “lifeTime” abwich, gab die Funktion “false” zurück und versetzte den Schwarm wieder in den Anfangszustand. So konnten wir, wenn jemand die Installation verlassen hatte, die Schwärme wieder für das Verfolgen von neuen Teilnehmern freigeben.

Implementierung der geplanten Funktionalitaeten

Was wir umsetzen wollten und letzten Endes umsetzen konnten, soll in folgendem Abschnitt erklaert werden.

Implementierung des Verdraengungsmodus

Neben dem grossen Umbau des Programms planten wir, einen weiteren Modus zu implementieren, der vorhin schon als Verdrängungsmodus beschrieben worden ist. Diesen steckten wir in eine Klasse namens “bubbleBath.cpp”. Anforderungen an die Klasse war, Elemente auf Knopfdruck erstellen und diese die Treppen herunterfallen lassen zu können, sowie “unsichtbare” Objekte zu verwalten, die die Teilnehmer auf dem Spielfeld verfolgen und durch einen Verdrängungsradius bei diesen den Eindruck erwecken, dass sie mit ihrem Körper durch ein Feld aus bunten Kugeln wandern können und diese dabei abstossen. Wir dockten diese Klasse an unser Model an (ebenso wie auch jeden anderen Modus, siehe Abbildung 3.3) und schalteten eine Unterklasse namens “floatingObject.cpp”, welche eben diese Verdrängungsradien enthält, bei Bedarf ein oder aus.

Anforderung an die Klasse “floatingObject.cpp” war die Verwaltung einer beliebigen Anzahl an Objekten, die in einem bestimmten Bereich der Projektion auf Teilnehmer warteten und sich je nach ein- oder ausgeschalteten boolean mit diesen in einer bestimmten Art und Weise bewegten - dabei aber unsichtbar blieben und dennoch Strukturen verdrängen konnten. Parallelen zum Tracking-Modus sind durchaus erkennbar jedoch gab es derart grosse Unterschiede in Komplexität und Verhalten, dass wir entschieden auf eine Vererbung der Funktionalitäten aus der Klasse “behaviour.cpp” zu verzichten, obwohl es sich auf den ersten Blick als elegante Lösung anbot.

Implementierung des Mappings

Ohne externes Mapping - AddOn mussten wir einen Weg finden den Bewegungsradius unserer Kreisschwärme auf ein festgelegtes Spielfeld zu begrenzen, um die Installation kompakt und übersichtlich zu halten. Die Klasse allObstacles lädt bei Programmstart ein Grundgerüst aus ofLines, welches sowohl die Treppen als auch die Bodenfläche enthält. Diese ofLines dienen zur Begrenzung unseres Aktionsfeldes, da sie für die Schwarmobjekte undurchlässig sind. Das Grundgerüst kann nun an jeder Ecke angeklickt und mit Hilfe der Tastatur frei verändert werden. Die gerade angewählte Ecke wird mit einem Gelben Punkt optisch hervorgehoben. Dies kann man auch in der nachfolgenden Abbildung gut erkennen. Des Weiteren passt sich das Spielfeld der Auflösung und der Anzahl der benutzten Beamer automatisch an. Dies ist sehr hilfreich, da wir zum Einen hauptsächlich für die „lange Nacht der Wissenschaften“ programmieren, gleichzeitig aber auch in der Lage sein müssen unser Projekt schnell und leicht anpassen zu können, da es beispielsweiße für die Abschlusspräsentation im kleinen Rahmen neu aufgebaut werden musste. Zusätzlich hatten wir nun die Möglichkeit diese Installation auf unterschiedlichste Grundflächen zu projizieren.

Die Klasse allObstacles.cpp

Das wichtigstes Elemente der Klasse allObstacles.cpp ist ein zwei dimensionaler Vector cornerPointArray, welcher aus ofVec2f - Objekten besteht, die sich wiederum jeweils aus einer X und einer Y - Koordinate zusammensetzen.

Hier werden die Koordinaten der Einzelnen Eckpunkte unter Berücksichtigung von Breite und Höhe der genutzten Anzeige berechnet. Im konkreten Fall der Projektion im Atrium kommen zwei Projektoren für die beiden Treppen, sowie ein Projektor - Tandem für die Bodenfläche zum Einsatz. Da die Anzeigen bei Verwendung eines Triple - Heads stets nebeneinander angeordnet werden, wird es nötig, das Koordinaten so in Relation zueinander gesetzt werden, dass am Ende ein Bild mit einfacher Breite aber dreifacher Höhe entsteht (1. Teil: Treppe 1, 2.Teil: Treppe 2 und 3. Teil: Bodenfläche).

Die Funktion „mousePressed()“ der testApp habe ich so ergänzt, dass es möglich ist Eckpunkte des Spielfeldes anzuklicken und über die Tastatur zu verschieben. Dies funktioniert so, dass die Maus - Koordinaten in einem ofVec2f zwischengespeichert und mit sämtlichen Eckpunkten des cornerPointArrays verglichen werden. Gibt es eine Übereinstimmung (Schwellenwert ist +/- 10 Pixel) so wird die entsprechende Ecke ausgewählt und gelb gekennzeichnet (Siehe Abbildung: Mapping Grundgerüst). Es wäre ebenfalls möglich gewesen eine Art Drag&Drop zu implementieren, welches den Umweg über die Tastatur zwecks verändern der Eckpunkte erspart hätte. Dies ließ ich aber sein, da wir uns stattdessen dafür entschieden eine Tablet - Remote zu programmieren, welche es uns ermöglicht das Programm dynamisch zur Laufzeit zu verändern, ohne dafür den Computer anfassen zu müssen. Des Weiteren bietet sie eine graphische Oberfläche welche sämtliche Einstellungsmöglichkeiten übersichtlich und strukturiert auflistet. Damit die Kreisschwärme auch bei Veränderung des Spielfeld - Grundgerüst ordentlich aufgereiht werden müssen ihre Initialpunkte neu berechnet werden. Hierfür gibt es die Methode „calculateInitialXandY()“.

Diese wird in jeder update - Methode aufgerufen, enthält das Array „initialPoints“ bereits Werte, so wird es geleert und die Punkte werden neu berechnet. Es werden Hilfslinie konstruiert anhand der hinterlegten Koordinaten in cornerPointArray, an denen sich die einzelnen Kreisschwärme nun orientieren. So wird gewährleistet, das auch bei einer anderen Projektionsfläche als den Treppen im Atrium des BB - Gebäudes, ein visuell zufrieden stellendes und aufgeräumtes Ergebnis zu verzeichnen ist.

In der Funktion „initField()“ werden nun die ofLines zwischen den Einzelnen Punkten des CornerPointArrays gezogen und im Vektor allElementsArray gespeichert.

Zusätzlich wird noch ein Startpunkt definiert, an dem neu erschaffene Objekte auftauchen werden. Eben jene Objekte, welcher später als Koordinaten der Ausstellungsstücke der Architekten dienen, können über die Funktion „initObstacle()“ hinzugefügt werden. Hier kann man zusätzlich zur Position auch den Radius angeben.

Mit diesen Verbesserungen gegenueber unserem urspruenglichen Code konnten wir das Projekt so versuchen technisch umzusetzen


Hardware zur Visuallisierung

Mit folgender Hardware realisierten wir dieses Projekt. Unter folgenden Links kann sich das Ergebnis unserer Arbeiten naeher angesehen werden.

http://youtu.be/Lh-_aeNTfQQ

https://youtu.be/l30NQ4O8iuc

https://youtu.be/49qwLg34yBo


Aufbau im Raum

Zur Verwirklichung der Projektion im Raum wurden insgesamt 2 Projektoren der Firma NEC benutzt.


Projektoren.png


An dem Projektoren für die Bodenbestrahlung kann man eines unserer Probleme erkennen, welches aber auf die räumlichen Gegebenheiten zurück zuführen ist. Wegen der geringen Deckenhöhe war es nicht möglich den Projektor wie gewünscht senkrecht direkt über die Bodenfläche zu installieren. Durch den schrägen Einfallwinkel des Projektors auf den Boden entsteht bei der Nutzung unseres Projekts ein Schlagschatten der Personen, der den Bereich der Bodenfläche hinter der jeweiligen Person verdeckt.


Aufbau im Atrium

Um das Projekt vom Raum ins Atrium zu bekommen waren einige Anpassungen notwendig. Insgesamt verwendeten wir 4 Beamer, 3 Laptops und ein iPad.

Systemaufbau zweite-02.png Grundinstallation 2-01.png

Die beiden eingebundenen Treppen wurden jeweils von einem Beamer aus dem ersten Stockwerk bestrahlt. Dort war auch der PC, der für die Visualisierung verantwortlich war, positioniert.

Die Bodenfläche wurde von einem Beamer - Tandem aus dem dritten Stockwerk ausgeleuchtet und erhielten ihr Bildsignal von dem Visualisierungs - PC aus dem ersten Stockwerk. Die Bildübertragung kam über eine HDMI - Verbindung, gewandelt in ein CAT - Signal zu Stande.

Um die drei Bildsignale (Treppe 1 und 2, sowie Bodenfläche) gebündelt von einem PC aus zu steueren, kam ein sogenannter Triple - Head zum Einsatz. Dieser erlaubt es die eigene Anzeige auf 3 Bildschirme zu erweitern.

2015-07-16 22.25.46.jpg

Das Beamer - Tandem war zusammen mit der Infrarot - Kamera, sowie dem Infrarot - Scheinwerfer an einer, von uns selbst entworfenen Konstruktion, befestigt. Neben dem Konstrukt ist der Tracking - PC positioniert, welcher die erfassten Daten wiederum per CAT an den Visualisierungs - PC im ersten Stock sendet. Die folgenden Abbildungen illustrieren unseren Systemaufbau.


Hier ein paar Impressionen von den esten Tests.

2015-06-22 22.08.47.jpg 2015-07-16 22.25.23.jpg

Zum Triplehead


-------------------Platzhalter Triplehead------------------------

Um die beiden Projektoren gemeinsam über einen Rechner zu betreiben haben wir ein externes Multi-Monitor-System der Firma „Matrox“ verwendet. Mit diesem System können über einen einzelnen Display-Anschluss eines Rechners drei Bildschirme oder Projektoren angeschlossen werden. Es kann eine einzelne Bildschirmfläche auf die angeschlossenen Darstellungsgeräte aufgeteilt oder für jedes Gerät eine eigene Bildschirmfläche erstellt werden. In unserem Fall wurde die Bildschirmfläche auf zwei Projektoren aufgeteilt.

Dank dem OpenFrameworks-Addon „ofxProjectionBlend“ aus Punkt 3 können die beiden Projektoren nicht nur nebeneinander sondern auch übereinander ausgerichtet installiert werden.

ProjectionBlend

Da wir im nächsten Semester größere Flächen bestrahlen möchten und logischerweise auch mehre Projektoren aneinander reihen müssen, habe wir uns vorab schon mit dem OpenFrameworks Addon „ofxProjectionBlend“ auseinander gesetzt. „ProjectionBlend“ dient dazu zwei oder mehre sich überlappenden Projektionen von der Helligkeit aneinander anzupassen. So kann man eine nahtlose Überblendung der beiden Projektionen erzeugen. Also kann man theoretisch x-beliebig viele Projektoren aneinander oder übereinander reihen. Bei unserem Prototypen wurde diese Funktion noch nicht richtig genutzt. Wegen den räumlichen Gegebenheiten (sehr hohe und breite Bodenleiste) war es grundsätzlich nicht möglich einen nahtlosen Übergang zu schaffen. „ofxProjectionBlend“ bietet aber auch ohne weitere Justierung der einzelnen Projektionen eine erleichterte Darstellung über mehre Projektoren. Dies kam uns auch bei unserem Prototypen zugute.

Bild des Prototypen in Aktion


Wie man auf der Abbildung 45 erkennen kann, ordnet das Addon die einzelnen Bildflächen immer von Links nach Rechts nebeneinander an, auch wenn der Übergang vertikal geschehen soll. Beispielsweise durch die Verwendung eines „TrippleHeads“ – Hardware für die Verwendung von mehreren Displays oder Projektoren an einem Rechner – werden die angeschlossenen Geräte als eine Bildschirmfläche erkannt, also horizontal nebeneinander angeordnet. Physikalisch ist es aber nun möglich die Projektoren nicht nebeneinander sondern übereinander anzuordnen.

Kalibierungsansicht des ofxProjectionBlend Addons


Aus der Sicht des Programmcodes habe ich das Addon mit der Methode „softEdgeSetup()“ initialisiert, welche in der setup()-Methode der Physics-Engine aufgerufen wird. Wie schon bereits beschrieben, wird auch hier für die nötigen Variablen auf meine XML-Klasse zurückgegriffen – „xml.softEdgeSettings();“. Über die Zeile „blender.setup( ... );“ wird auf die setup()-Methode des Addons zugegriffen, hier werden die Variablen aus dem XML-File mitgegeben „projection_width“, „projection_height“, „projection_count“, „pixel_overlap“. Bei der Wahl, die Überblendung vertikal laufen zu lassen, muss noch der Wert „ofxProjectorBlend_Vertical“ überreicht werden. Bleibt es bei der horizontalen Überblendung kann der Wert komplett ignoriert werden. In unserem Fall wird die vertikale Variante genutzt.

Das Addon „ofxProjectionBlend“ geht Hand in Hand mit dem Addon „ofxSimpleGuiToo“. Durch dieses GUI ( Abk. für engl. „graphical user interface“, zu Deutsch „grafische Benutzeroberfläche“) können die Luminanz, der Gammawert und die Stärke der Überblendung per „Slider“ live zum laufenden Programm geändert werden. Der Aufruf der GUI und das hinzufügen der einzelnen Schaltflächen geschieht über das Kürzel „gui.“. In meinem Fall habe ich Überschriften „addTitle“, On/Off-Schalter „addToggle“ und Schiebregler bzw. Slider „addSlider“ verwendet. Toggles verwenden als Wert einen Boolean und Slider beziehen sich auf einen Float-Wert. Auch ein automatisches bzw. manuelles Speichern der Werte ist über die GUI möglich. Das Addon legt allerdings selbst ein XML-File an, in diesem liegen auch nur die vom GUI verwendeten Variablen „showBlend“, „showCalibration“, „blendPower“, „gamma“, „luminance“.

"softEdgeSetup()"-Methode, "testApp.cpp" Physics-Engine


Für eine korrekte Darstellung muss natürlich auch die draw()-Methode unserer Physics-Engine angepasst werden. Da wir aus reinen Testzwecken für die Physics-Engine oft nur einen Bildschirm oder Projektor benutzten, habe ich eine if-Bedienung eingebaut, die sich auf die gewählte Anzahl der Projektoren bezieht, die in meinem XML „softEdge_settings.xml“ festgelegt ist. Wird hier nun eine 1 oder aus Fehlergründen eine 0 übergeben, wird der „Blender“ nicht gezeichnet sondern nur der Inhalt unserer Physics-Engine. „else“ steht hier nun für einen Wert der Variable „projector_count“ der sich über 1 befindet. Ist dies der Fall wird der „Blender“ mit „blender.begin();“ gezeichnet. Soll der „Blender“ gemalt werden beginnt sofort die nächste if-Schleife. Nun wird sich auf den Boolean „showCalibration“ bezogen, dieser kann auch im GUI verändert werden. Hat der Boolean den Wert 1 bzw. „true“ wird das Programmfenster mit einem Schachfeldmuster bemalt, dieses Muster ermöglicht eine leichtere Kalibrierung der Überblendung und Ausrichtung der Projektoren. Wenn der Boolean aber den Wert 0 bzw. „false“ hat, wird das Schachfeldmuster ausgeblendet und die eigentliche Darstellung der Physics-Engine wird angezeigt.

"draw()"- Methode, "testApp.cpp" Physics Engine


Die danach folgenden Zeilen sind fast selbsterklärend. Ist man mit dem Inhalt, auf den sich der „Blender“ beziehen soll, fertig geschrieben, kennzeichnet man dies mit der Zeile „blender.end();“. Um anschließend alles darzustellen, wird die Funktion „blender.draw();“ für den Inhalt des „Blenders“ und „gui.draw();“ für die erzeugten Schaltflächen des GUI ausgeführt.


Zur Tablet Remote

Mit dem Umfang unserer Software wachsen natürlich auch die Bedienungsmöglichkeiten. Im ersten Projektabschnitt war es noch kein Problem dies über die rechnereigene Tastatur zu bewerkstelligen. Doch im zweiten Projektabschnitt kamen wir schnell an die Grenzen der einfachen Handhabung über die Tastatur. Funktionalitäten wie das Transformieren unseres Spielfeldes, die Platzierung verschiedener Objekte und auch einfachere Einstellungen wie das Aktivieren des Vollbildmodus lassen sich so fast nicht mehr ohne ein Handbuch bedienen. Um die Bedienung so einfach wie möglich zu gestalten, habe ich mich für eine Remote-Applikation für „Tablets“ und „Smartphones“ entschieden.

Touch OSC

Die Applikation „TouchOSC“ besteht aus zwei Teilen. Der Applikation selbst, die auf dem Endgerät, also dem Tablet oder Smartphone, selbst läuft und einem Editor mit dem man die Oberfläche komplett frei gestalten und anpassen kann. Sowohl die Applikation als auch der Editor sind für fast alle Plattformen verfügbar.

Der Editor bietet eine verständliche Übersicht aller möglichen Funktionen. Es werden verschiedene Layoutvorlagen angeboten. Unter anderem für „iPhone/iPod Touch“, für „iPad“, für „iPhone 5“ aber auch „Custom“, was die Möglichkeit bietet die Größe selbst zu justieren. Hat man nun eine passende Größe eingestellt, kann man per Rechtsklick auf die angezeigte Darstellung des leeren Layouts Kontrollfunktionen hinzufügen. Zu diesen Kontrollfunktionen gehören zum Beispiel „Push Buttons“, Toggle Buttons“, „XY-Pads“ und auch „Fader“. Für unsere Remote habe ich hauptsächlich „Toggle Buttons“ und „Fader“ benutzt. Um die Orientierung bei einer steigenden Anzahl an Kontrollfunktion zu behalten ist es auch möglich weitere Tabs anzulegen.

Jeder Tab bzw. jede Seite des Layouts ist eindeutig benannt. Standardmäßig wird von 1 ab nach oben gezählt, es kann aber auch eine eigene Bezeichnung gewählt haben. Daher heißen unsere Seiten „/sttng“, „/se“, „/save“ und „/me“. Genau wie die einzelnen Seiten sind auch die Kontrollelemente genau benannt. So hat zum Beispiel der Button für „Fullscreen“ die Bezeichnung „fs“ und der Button für „Engine Idle“ heißt „idle“. Aus der Kombination des Seiten Namens und der des jeweiligen Buttons entsteht die sogenannte OSC-Adresse. Auch dieser kann aber auch wieder selbst definiert werden. In folgender Abbildung kann das Prinzip unseres Layouts gut erkannt werden.

Abbildung 3.7.PNG

OSC Anbindung in der Physics Engine

Die Kommunikation funktioniert wie bei allen unserer Softwarekomponenten auch zwischen der Tablet Remote und unserer Physics-Engine über OSC. Die vom Tablet ausgehenden OSC-Nachrichten werden von einem eigens dafür in der Physics-Engine initialisierten OSC-Receivers aufgefangen. Anschließend an die Funktion „checkPadMessage(ofxOscMessage m)“ weitergegeben.

Diese Funktion erfragt die gesetzte Adresse der OSC-Nachricht und führt die jeweilige Funktion dafür aus. Hat die einkommende Nachricht zum Beispiel die Adresse „/sttng/fs“, dann wird die Funktion „toggleFullscreen()“ ausgeführt und somit die Software in den Vollbildmodus versetzt. Die in Abbildung 3.11 gezeigten Bedingungen zeigen nur einen kleinen Teil der Funktionalität. Insgesamt habe ich 45 verschiedene Abfragen implementiert die alle jeweils auf einen eigenen Button unserer Tablet Remote hören. � Nicht nur das Empfangen von Nachrichten die von unserem Tablet kommen, auch das Senden an unser Tablet ist realisierbar. Dies gibt uns die Möglichkeit wichtige Werte, wie die Frames-Per-Second ( kurz FPS ), die ID des aktuell angewählten Objektes oder eine einfache Rückmeldung verschiedener Zustände von Buttons zu liefern. Dies heißt so viel wie, man kann auf unserer Tablet Remote erkennen, welche Funktion aktiv und welche inaktiv ist, aber auch welchen Wert zum Beispiel ein bestimmter Fader gerade hat.

Die Funktion „syncOscPad()“ sorgt dafür, dass an unser Tablet immer die aktuellen Werte der Software übergeben werden. Für jeden Wert, der an das Tablet gesendet werden soll, wird eine eigene OSC-Nachricht erstellt. Diese einzelnen Nachrichten werden in ein OSC-Bundle zusammengeführt. Dies ermöglicht es mehre Nachrichten gleichzeitig zu verschicken.

Media Interface

Das Media Interface stellt eine komplett neue Softwarekomponente unseres gesamten Projektes da. Mit ihm sollen aktuell genau zwei Aufgaben abgedeckt werden: die Ausgabe von Audiodateien bei Kollisionsevents und die Ansteuerung eines DMX-USB-Interfaces, um die Farbe eines Kreisschwarms an einen oder mehrere Scheinwerfer zu übertragen.

Durch die Auslagerung dieser Funktionen wurde zudem auch die Performance unserer Physik-Engine verbessert, da sie in der Version unseres Prototypen noch die Ausgabe von Audiomaterial mitübernommen hatte.


DMX

Digital Multiplex, abgekürzt DMX, „ist ein digitales Steuerprotokoll, das in der Bühnen- und Veranstaltungstechnik zur Steuerung von Dimmern, „intelligenten“ Scheinwerfern, Moving Heads und Effektgeräten angewandt wird“.

Seit dem ersten Projektabschnitt steht eine Kooperation mit der Fakultät Architektur bei der Langen Nacht der Wissenschaften im Raum. Diese Kooperation sieht es vor, dass verschiedene Ausstellungsobjekte der Architekturstudenten des 2. Semesters in nächster Nähe zu unserer Installation ausgestellt werden sollen. Um eine Verbindung zwischen unserer Installation und den Ausstellungsobjekten zu schaffen, sollte eine Möglichkeit geschaffen werden, diese und auch andere Objekte per Scheinwerfer zu bestrahlen. Dabei war es wichtig, dass dies im richtigen Moment, genauer wenn sich eine Person auf einen bestimmten Abstand einem Objekt nähert, und auch in der entsprechenden Farbe des Kreisschwarms der sich annähernden Person passiert.

Da es aber noch keine genaueren Details seitens der Fakultät Architektur über die Ausstellungsobjekte gab, haben wir für die aktuelle Umsetzung Prototypen aus Holz und Stoff gebaut.

Softwareseitig habe ich mit dem OpenFrameworks Addon „ofxDmx“ von Kyle McDonald gearbeitet. Zum einen ist die Handhabung dieses Addons sehr einfach gehalten und es wurde komplett auf die Benutzung mit Produkten der Marke „Enttec“ ausgelegt. So fiel auch die Wahl der Hardware auf das „„Enttec DMX USB Pro MK2 Interface“, welches uns von der Hochschule zur Verfügung gestellt wurde.

Bei der Umsetzung der Dmx-Steuerung auf der Softwareseite, gibt es zwei wichtige Funktionen: „dmxSetup() (siehe Klasse dmxSetup – Zeile 252) und setSpotColor() (siehe Klasse dmxSetup – Zeile 260). Beim Setup wird mit dem Ausdruck dmx.connect(...) die Verbindung zu unserem DMX USB Interface erstellt. Die Werte in der Klammer „DMXUSB“ und „devices*channelsPerDevice“ stehen für den im System zugewiesenen Namen des Interfaces „tty.usbserial-ENWE0E0D“ und für die Anzahl der gesamten Dmx-Channels die benutzt werden sollen. Diese entsteht hier aus dem Produkt der angegebenen Anzahl der angeschlossenen Scheinwerfer und der Menge der genutzten Channels pro Scheinwerfer. Mit der Funktion „dmx.update(true)“ wird lediglich der Wert aller Channels so gesetzt dass die Scheinwerfer beim Start des Programms in den Ausgangszustand, also auf Schwarz, gestellt werden.

Audio Ausgabe

Damit alle medialen Events gesammelt über eine Software laufen, habe Ich auch die Ausgabe der Audiodateien in das Media-Interface mit eingebunden. Dafür wurde der „ofSoundPlayer“ genutzt, der bereits Bestandteil von OpenFrameworks ist.

Audioevents sind zum einen die Kollision zweier oder mehrerer Kreisschwärme, welche mit einem „Clap“-Ton wiedergegeben wird, aber auch das Abspielen der allgemeinen Hintergrundmusik.

Man kann in den „ofSoundPlayer“ grundsätzlich eine Audiodatei laden ( .loadSound(...) und die Lautstärke ( .setVolume(1.0) ) oder das Tempo ( .setSpeed(0.96) ) dieser anpassen. Der „ofSoundPlayer“ gibt aber auch Möglichkeiten eine Audiodatei, wie die unserer Hintergrundmusik, in einer Dauerschleife abzuspielen oder ein gleichzeitiges Mehrfachabspielen einer Audiodatei zu ermöglichen, wie die unseres „Clap“-Tones.

Die Kommunikation zwischen der Physik-Engine und dem Media-Interface funktioniert, wie auch schon bei der DMX-Steuerung, über das OSC-Protokoll. statt. Bei einer, in der Physik-Engine, entstandenen Kollision wird eine OSC-Nachricht mit der Adresse „/clap“ verschickt. Diese beinhaltet lediglich den Wert „1“. Bei Erreichen der entsprechenden Nachricht ruft das Media-Interface die Funktion „playClap()“ auf.

Die Hintergrundmusik kann entweder über das Media-Interface selbst gestartet werden oder per Nachricht, mit der Adresse „/music“ von der Physik-Engine. Wie die Nachricht per Physics-Engine übermittelt wird, wird im Kapitel „Tablet Remote“ noch genauer erklärt.


Fazit

Vor allem fachlich und menschlich haben wir im Verlauf unserer praktischen Studienarbeit viel gelernt. Der Hauptgrund der positiven Zusammenarbeit war, dass in der Gruppe jederzeit eine gute Stimmung herrschte und sich jede Person ins Projekt einbrachte. Auch die gute Begleitung und Hilfestellung unserer Betreuer leistete hierzu einen nicht zu unterschätzenden Beitrag.

Fachlich nahmen wir vor allem im Bereich der C++ Programmierung viele neue Erkenntnisse mit. Auch das Zusammenbringen der Einzelkomponenten und das Nutzen von Wissen aus vorhergehenden Semestern (z.B. Interaktion) schaffte einen praktischen Anwendungszweck.

Vor allem die erfolgreiche Abschlusspräsentation und die positiven Rückmeldungen, haben uns darin bestätigt, dass wir vieles im Aufbau und Entwicklungsprozess richtig gemacht hatten und auf einem guten Weg sind