Der Hype um Augmented Reality Anwendungen ist überall zu spüren. Fast jeder von uns hat schon in irgendeiner Form solche Anwendungen gesehen. Angefangen hat es mit der Mini Kampagne, die auch mehrere Awards bekommen hat.

In diesem Artikel möchte ich kurz vorstellen wie man mit Hilfe einer handelsüblichen Webcam und dem Flashplayer 10 eine browserbasierte Augumented Reality Anwendung erstellt.

Als Entwicklungsumgebung benutze ich in diesem Beispiel FlashDevelop. FlashDevelop ähnelt dem FlashBuilder von Adobe, ist OpenSource und sehr gut geeignet um Actionscript Anwendungen zu entwickeln.

Als erstes müssen wir FlashDevelop für Actionscript konfigurieren. Dafür laden wir uns von flashdevelop.org die aktuelle Version herunter und besorgen uns von labs.adobe.com das Flex SDK. Nachdem wir FlashDevelop installiert und das Flex SDK extrahiert haben starten wir FlashDevelop und  gehen über Tools > Program Settings zu den allgemeinen Programmeinstellungen. In dem Settings Fenster wählen wir den zweiten Eintrag AS3Context und wählen für das Feld Flex SDK Location den Ordner in den wir das Flex SDK extrahiert haben. Jetzt ist FlashDevelop bereit zur Entwicklung von Flex bzw. Actionscript 3 Projekten.

Für unsere Augumented Reality Anwendung benötigen wir als erstes den FLARManager. FLARManager baut auf dem FLARToolkit auf und macht den Umgang mit Kamera und Marker wesentlich einfacher.  Zum Download gibt es den ihn unter words.transmote.com/wp/flarmanger ,dem Blog von Erik Socolofsky. Nachdem wir das Zip Archiv herunter geladen haben entpacken wir es und finden darin ein FlashBuilder Projekt mit einem Unterordner src. Diesen Unterordner verknüpfen wir jetzt mit FlashDevelop in dem wir in FlashDevelop über Tools > Global Classpathes den Pfad zu dem src Ordner hinzufügen. Jetzt kennt FlashDevelop auch die FLARManager Klassen.

FlashDevelop Global Class Pathes

Globale Klassen in FlashDevelop festlegen

Als nächstes legen wir in FlashDevelop ein neues AS3 Projekt an in dem wir über Project > New Project die Vorlage AS3 Project auswählen. FlashDevelop legt jetzt für uns die Ordnerstruktur und die Actionscript Datei main.as an, die auch unsere Ausgangsdatei werden wird.

Ein neues Actionscript 3 Projekt in FlashDevelop anlegen.

Ein neues Actionscript 3 Projekt in FlashDevelop anlegen.

Unsere Augumented Reality Anwendung soll einen dreidimensionalen Würfel darstellen sobald ein Marker von der Webcam erkannt wird und der Würfel soll dann dem Marker folgen. Um den Würfel in Flash darzustellen werde ich in diesem Beispiel Papervision3D benutzen. Es ist natürlich möglich auch andere 3D Engines wie z.B. Away3D oder Sandy mit dem FLARManager anzusprechen. Damit ich Papervision in FlashDevelop benutzen kann lade ich mir von papervision.org die aktuellen Klassen runter und binde sie wieder wie die FLARManager Klassen über Global Classpathes in FlashDevelop ein.

Als nächstes benötigen wir einen Marker auf den unsere Anwendung reagieren soll. Für unser Beispiel nehmen wir aus dem FLARManager Ordner unter resources > flar > patterns > pat die Datei patt001.pat und kopieren diese in unser Projekt in den Ordner src. Als Alternative kann man unter flash.tarotaro.org/blog mit dem ARToolkit Marker Generator aus einem Bild oder mit der Webcam ein Pattern File erzeugen. Jetzt brauchen wir noch aus dem FLARManager Ordner unter resources > flar die Dateien FLARCameraParams.dat und flarConfig.xml. Beide Dateien kopieren wir wieder in unser Projekt in den Ordner src. Die Datei flarConfig.xml beinhaltet verschiedene Einstellungsparameter für unsere Anwendung, die wir auch noch ein anpassen werden. Dazu öffnen wir in FlashDevelop die Datei flarConfig.xml und ändern als erstes den Pfad zu der FLARCameraParams.dat in src/FLARCameraParams.dat. Als nächstes passen wir den Pfad zu unserer Marker Datei ebenso an und löschen alle weiteren Marker daher wir in diesem Beispiel nur mit einem Marker arbeiten werden. Die Datei FLARCameraParams.dat brauchen wir nicht zu bearbeiten.

In FlashDevelop gehen wir jetzt noch über Project > Prpperties in die Projekteinstellungen und ändern die Größe der SWF Datei in 640 x 480 Pixel und passen den Ausgabepfad so an das die SWF Datei in unserem root Verzeichnis des Projektes generiert wird.

Jetzt können wir damit beginnen unseren Actionscript 3 Code zu schreiben. Wir öffnen die Datei main.as und sehen das FlashDevelop schon das Klassengerüst angelegt hat.

Als erstes definieren wir die Variablen für unsere Anwendung :

private var fm:FLARManager;
private var scene:Scene3D;
private var view:Viewport3D;
private var camera:FLARCamera3D;
private var re:LazyRenderEngine;
private var cube:Cube;
private var marker:FLARMarker;
private var con:DisplayObject3D;
private var materials:MaterialsList;

Dann legen wir eine Funktion initFLAR an in der der FLARManger initialisiert wird. In dem Konstruktor für den FLARManger geben wir den Pfad zu der Datei flarConfig.xml an. Der FLARManager bekommt die beiden EventListener MARKER_ADDED und MARKER_REMOVED, für die wir die beiden Funktionen onAdded bzw. onRemoved erstellen, und wird mit addChild(Sprite(fm.flarSource)) in die Displayliste eingefügt. Dann sieht unsere initFLAR Funktion wie folgt aus:

private function initFLAR():void {
   fm = new FLARManager("flarConfig.xml");
   fm.addEventListener(FLARMarkerEvent.MARKER_ADDED, onAdded);
   fm.addEventListener(FLARMarkerEvent.MARKER_REMOVED, onRemoved);
   addChild(Sprite(fm.flarSource));
}

Als nächstes kümmern wir uns um die onAdded und onRemoved Funktion. Wenn die Kamera ein Marker erkennt tritt das Ereignis MARKER_ADDED ein, wenn der Marker wieder verschwindet das Ereignis MARKER_REMOVED. Bei dem Ereignis MARKER_ADDED sagen wir einfach das unsere Variable marker der aktuell zu sehende Marker ist: marker = event.marker, bei dem Ereignis MARKER_REMOVED sagen wir das kein Marker vorhanden ist: marker = null.

Jetzt benötigen wir noch unsere 3D Szene. Dafür legen wir die Funktion init3D an. In der Funktion initialisieren wir als erstes unsere Variable scene mit scene = new  Scene3D(). Danach initialisieren wir unsere Variable camera mit camera = new FLARCamera(fm.cameraParams), dabei übergeben wir ein Parameter der auf die Datei FLARCameraParams.dat die wir vorher als Parameter in der flarConfig.xml definiert haben. Als nächstes definieren wir noch view als Viewport3D und benutzen die LazyRenderEngine. Dann sollte unsere init3D Funktion bisher so aussehen:

private function init3D():void {
   scene = new Scene3D();
   camera = new FLARCamera3D(fm.cameraParams);
   camera.z = -30;
   view = new Viewport3D(640, 480, true);
   re = new LazyRenderEngine(scene, camera, view);
}

init 3D

Die 3D Szene einrichten.

Damit wir in unserer 3D Szene auch etwas sehen benötigen wir noch ein 3D Objekt. Dazu erstellen wir mit Papervision einen Würfel, dem wir in diesem Beispiel eine einfache blaue Farbe geben, er könnte aber auch mit Bildern oder Videos texturiert werden. Zum besseren Handling erstellen wir ein 3D Container in dem wir unseren Würfel platzieren. So muss nur der Container von dem Marker beeinflusst werden und wir könnten bequem noch mehr Elemente in unseren Container platzieren die alle gleich transformiert werden würden. Dem Würfel geben wir die Eigenschaft visible = false damit er erst zu sehen ist wenn ein Marker erkannt ist. Als letztes erstellen wir noch ein EventListener bei ENTER_FRAME der die Funktion loop aufruft. Jetzt sollte unsere init3D Funktion so aussehen:

private function init3D(event:Event):void {
   scene = new Scene3D();
   camera = new FLARCamera3D(fm.cameraParams);
   camera.z = -30;
   view = new Viewport3D(640, 480, true);
   re = new LazyRenderEngine(scene, camera, view);
   materials = new MaterialsList();
   materials.addMaterial(new ColorMaterial(0x24479b, 1), "top");
   materials.addMaterial(new ColorMaterial(0x24479b, 1), "left");
   materials.addMaterial(new ColorMaterial(0x24479b, 1), "bottom");
   materials.addMaterial(new ColorMaterial(0x24479b, 1), "right");
   materials.addMaterial(new ColorMaterial(0x24479b, 1), "back");
   materials.addMaterial(new ColorMaterial(0x24479b, 1), "front");
   cube = new Cube(materials, 100, 100, 100);
   cube.visible = false;
   con = new DisplayObject3D();
   con.addChild(cube);
   scene.addChild(con);
   addChild(view);
   addEventListener(Event.ENTER_FRAME, loop);
 }

In der initFlar Funktion fügen wir jetzt noch den EventListener INIT ein damit unsere 3D Szene erst erstellt wird wenn der FLArManager initialisiert ist: fm.addEventListener(Event.INIT, init3D);

Jetzt brauchen wir nur noch unsere loop Funktion zu erstellen. In der Funktion wird als erstes abgefragt ob ein Marker zu sehen ist. Wenn ja, dann wird unser 3D Container passend zu dem Marker transformiert. Wenn kein Marker zu sehen ist passiert nichts. Nach der Abfrage wird unsere Anzeige neu gezeichnet in dem wir von unserer Render Engine die Funktion render aufrufen. Jetzt sollte die loop Funktion wie folgt aussehen:

private function loop(event:Event):void {
   if (marker != null) {
     con.transform = FLARPVGeomUtils.convertFLARMatrixToPVMatrix(marker.transformMatrix);
   }
   re.render();
}

Die fertige Klasse

Damit unser Würfel nur zusehen ist wenn ein Marker da ist fügen wir in der onAdded Funktion noch die Zeile cube.visible = true ein, und damit er wieder verschwindet wenn kein marker zu sehen ist fügen wir in der onRemoved Funktion die Zeile cube.visible = false ein.

Jetzt sollte unsere komplette Klasse wie folgt aussehen:

package {
   import com.transmote.flar.FLARManager;
   import com.transmote.flar.marker.FLARMarker;
   import com.transmote.flar.marker.FLARMarkerEvent;
   import flash.display.Sprite;
   import flash.events.Event;
   import org.libspark.flartoolkit.support.pv3d.FLARCamera3D;
   import org.papervision3d.core.proto.DisplayObjectContainer3D;
   import org.papervision3d.materials.utils.MaterialsList;
   import org.papervision3d.objects.DisplayObject3D;
   import org.papervision3d.objects.primitives.Cube;
   import org.papervision3d.render.LazyRenderEngine;
   import org.papervision3d.scenes.Scene3D;
   import org.papervision3d.view.Viewport3D;
   import com.transmote.flar.utils.geom.FLARPVGeomUtils;
   import org.papervision3d.materials.ColorMaterial;
   /**
   * ...
   * @author Jörg Dittmann
   */
   public class Main extends Sprite {
      private var fm:FLARManager;
      private var scene:Scene3D;
      private var view:Viewport3D;
      private var camera:FLARCamera3D;
      private var re:LazyRenderEngine;
      private var cube:Cube;
      private var marker:FLARMarker;
      private var con:DisplayObject3D;
      private var materials:MaterialsList;
   public function Main():void {
      if (stage) init();
      else addEventListener(Event.ADDED_TO_STAGE, init);
   }
   private function init(e:Event = null):void {
      removeEventListener(Event.ADDED_TO_STAGE, init);
      // entry point
      initFLAR();
   }
   private function initFLAR():void {
      fm = new FLARManager("src/flarConfig.xml");
      fm.addEventListener(FLARMarkerEvent.MARKER_ADDED, onAdded);
      fm.addEventListener(FLARMarkerEvent.MARKER_REMOVED, onRemoved);
      fm.addEventListener(Event.INIT, init3D);
      addChild(Sprite(fm.flarSource));
   }
   private function onAdded(event:FLARMarkerEvent):void {
      marker = event.marker;
      cube.visible = true;
   }
   private function onRemoved(event:FLARMarkerEvent):void {
      marker = null;
      cube.visible = false;
   }
   private function init3D(event:Event):void {
      scene = new Scene3D();
      camera = new FLARCamera3D(fm.cameraParams);
      camera.z = -30;
      view = new Viewport3D(640, 480, true);
      re = new LazyRenderEngine(scene, camera, view);
      materials = new MaterialsList();
      materials.addMaterial(new ColorMaterial(0x24479b, 1), "top");
      materials.addMaterial(new ColorMaterial(0x24479b, 1), "left");
      materials.addMaterial(new ColorMaterial(0x24479b, 1), "bottom");
      materials.addMaterial(new ColorMaterial(0x24479b, 1), "right");
      materials.addMaterial(new ColorMaterial(0x24479b, 1), "back");
      materials.addMaterial(new ColorMaterial(0x24479b, 1), "front");
      cube = new Cube(materials, 100, 100, 100);
      cube.visible = false;
      con = new DisplayObject3D();
      con.addChild(cube);
      scene.addChild(con);
      addChild(view);
      addEventListener(Event.ENTER_FRAME, loop);
   }
   private function loop(event:Event):void {
      if (marker != null) {
         con.transform = FLARPVGeomUtils.convertFLARMatrixToPVMatrix(marker.transformMatrix);
      }
      re.render();
   }
 }
}

Das Ergebnis

So sollte das Ergebnis aussehen.

Ein Beispiel für eine Augmented Reality Anwendung finden Sie unter: http://spielwiese.ahhhkah.de