Creating an augmented reality site using Papervision 3d and FLARtoolkit. Part 2: Listening for Events and Animating the Scene

Welcome back for part 2. In the first tutorial I went over how to set up your project, create a PV3D object to attach, and call the functions that will attach this object to your marker (if you haven’t read part 1 yet, check it out below). In part 2, I will go over how to listen for when the marker is detected (and undetected). We will also look at how the animation is triggered and stopped.

Now that we have the basic setup, the next step is to animate our PV3D scene. For almost all Flash animations, I use TweenLite or TweenMax, they are really great tweening engines with a lot of features. The best part? They are free. Back in MainDisplay.as, I set up public functions that would animate the various objects I had set up earlier. Working from our previous example variable of “_introFlash1”, here is the initial animation function that gets called (obviously this is just the start of the animation for example purposes. Yours can be as complex as you want):

public function startAni():void {
TweenLite.to(_introFlash1, .5, {alpha:1, onComplete:nextAni});
TweenLite.to(_introFlash1, .5, {scale:1, overwrite:0});
}

Just a simple scale in and alpha in, from the starting values we set previously. Another nice thing about this tween class is that it is easy to call an “onComplete” function when the animation finishes. In this way you can trigger sequential animations easily. Fun stuff. At this point you can also write your function to stop the animation. It basically reverses the stopping animation by tweening back to the original values:

public function stopAni():void {
TweenLite.to(_introFlash1, .5, {alpha:0, onComplete:aniDone});
TweenLite.to(_introFlash1, .5, {scale:.01, overwrite:0});
}

Ok, now that those functions are ready, we want to set up our listeners that check to see if our marker is detected. If it is, then we attach our PV3D object and animate it. When I first set about doing this, I had it set up so that the animation would play while the marker continued to be detected. However, this sometimes caused the animation to not play smoothly. What I ended up doing was making it so that once the marker was detected and the object attached, the detection would stop and the animation would play through. Once the animation was done, the marker detection would start up again. Another issue with the marker detection was that it tends to be very sensitive. Although you can adjust this sensitivity somewhat, I still found it to be too twitchy – the instant the marker was undetected it would fire an undetection event, even thought he marker was still clearly still there. To remedy this, I set a timer that gets triggered by the undetection event. This way there would be some needed wiggle room to give the user a more consistent experience. Let’s take a look at how these listeners and timers come into play. But first, let’s set up our custom events. In most AS projects, you will find it helpful to create custom events and this one is no different. So create a new Actionscript file (I named mine “FlarEvents”) and declare your new events:

public class FlarEvents extends Event {
public static const SCENE_ANIMATED_IN:String = “scene_animated_in”; //fired when scene has finished animating in
public static const SCENE_ANIMATED_OUT:String = “scene_animated_out”; //fired when scene has finished animating out
public static const FLAR_DETECTED:String = “flar_detected”; //fired when the marker is detected
public static const FLAR_UNDETECTED:String = “flar_undetected”; //fired when the marker is undetected
public function FlarEvents(type:String,bubbles:Boolean=false,cancelable:Boolean=false) {
super(type,bubbles,cancelable);
}
}

Back in “FlarSpace.as”, we first declare some variables:

private var _played:Boolean = false; //tells if the animation has played
private var _detected:Boolean = false; //tells if the marker has been detected
private var _undetectTimer:Timer = new Timer(0, 8); //sets a timer for when the marker is undetected

Now to set up some listeners:

this.addEventListener(Event.ENTER_FRAME, mainEventListener);

This listener is our main listener that renders the PV3D scene and constantly checks to see if the marker is detected. Let’s check it out.

private function mainEventListener(e:Event):void {
_capture.draw(_myVideo); //constantly redraws the video feed from our webcam
try {
//the _detector.getConfidence() comparison value (0.6 in my file) is the threshold the FLARtoolkit uses to detect
//the marker via the webcam. Try adjusting this value to make it more or less sensitive.
if (_detector.detectMarkerLite(_raster,128)&&_detector.getConfidence()>0.6) {
_detector.getTransformMatrix(_trans); //get the 3d position our marker
_mainContainer.setTransformMatrix(_trans); //set our PV3D object to the position of our marker
_renderer.renderScene(_scene3D, _cam3D, _viewPort); //render our PV3D scene
this.dispatchEvent(new Event(Events.FLAR_DETECTED)); //dispatch the FLAR_DETECTED event into the event flow
_detected=true; //marker has been detected, set _detected to true
}
//this checks if the detection falls below a certain threshold, I had good luck with 0.3.
//It also check to verify that the animation has played.
if (_detector.detectMarkerLite(_raster,128)&&_detector.getConfidence()<0.3&&_played==true) {
_detected = false; //we have lost the marker, set _detected to false
_undetectTimer.start(); //start the undetection timer
}
} catch (e:Error) {
}
}

The second two listeners listen for two of the custom Events that I created.

this.addEventListener(Events.FLAR_DETECTED, flarDetected);
this.addEventListener(Events.FLAR_UNDETECTED, flarUndetected);

Let’s take a look at the functions that are triggered by these events:

private function flarDetected(evt:Event):void{
this.removeEventListener(Events.FLAR_DETECTED, flarDetected); //remove the FLAR_DETECTED calling event listener
this.removeEventListener(Event.ENTER_FRAME, mainEventListener); //remove the main event listener
this.addEventListener(Event.ENTER_FRAME, aniEventListener); //add a new event listener to listen for the starting animation to finish.
_main.startAni1(); //call our public function to start the animation back in our MainDisplay class*.
//*Remember, _main is our instantiation of this class that we added to our _mainContainer FLARBaseNode object in part 1.
}
private function flarUndetected(evt:Event):void{
this.removeEventListener(Events.FLAR_UNDETECTED, flarUndetected); //remove the FLAR_UNDETECTED calling event listener
this.removeEventListener(Event.ENTER_FRAME, mainEventListener); //remove the main event listener
this.addEventListener(Event.ENTER_FRAME, aniEventListener); //add a new event listener to listen for the ending animation to finish.
_main.stopAni1(); //call our public function to stop the animation back in our MainDisplay class.
}

What we are doing is removing the main event listener and setting up an alternate animation event listener. Here is what that looks like:

private function aniEventListener(e:Event):void{
_renderer.renderScene(_scene3D, _cam3D, _viewPort); //continue to render our PV3D scene
_mainContainer.setTransformMatrix(_trans); //set our PV3D to the last position of our marker
}

We are still triggering two of the behaviors in the main event listener: to make sure our PV3D object continues to be rendered, and place it at the last marker position. What’s missing is the constant marker detection, which tends to slow the animation down as mentioned above.

Now let’s take a look at the undetection logic. First, add a listener to the timer that is triggered when the timer ends:

_undetectTimer.addEventListener(TimerEvent.TIMER_COMPLETE, undetectTimerDone);

Then simply start the timer when the undetection event occurs (this is in our main event listener).

_undetectTimer.start();

And finally, the resulting function:

private function undetectTimerDone(evt:TimerEvent):void{
_undetectTimer.stop(); //stop the timer
_undetectTimer.reset(); //reset it for later use
this.dispatchEvent(new Event(Events.FLAR_UNDETECTED)); //dispatch the FLAR_UNDETECTED event into the event flow
}

By providing a delay in firing this event, we can ensure that it isn’t fired too quickly.

Ok, almost done. As of now we have the animation event listener taking care of the rendering and we are playing our animation. When the animation finishes, we want to turn the main event listener back on so that our object can once again be attached to the position of our marker. A couple of new event listeners should do the trick:

_main.addEventListener(Events.SCENE_ANIMATED_IN, aniIn); //listen for our custom event that is triggered when the initial animation finishes
_main.addEventListener(Events.SCENE_ANIMATED_OUT, aniOut); //listen for our custom event that is triggered when the ending animation finishes

And the corresponding functions:

private function aniIn(event:Event):void{
_played=true; //set out played variable to true
this.addEventListener(Event.ENTER_FRAME, mainEventListener); //start our main event listener back up again
this.removeEventListener(Event.ENTER_FRAME, aniEventListener); //remove our animation event listener
this.addEventListener(Events.FLAR_UNDETECTED, flarUndetected); //re-start the FLAR_UNDETECTED event listener
}
private function aniOut(event:Event):void{
_played=false; //set our played variable to false
this.addEventListener(Event.ENTER_FRAME, mainEventListener); //start our main event listener back up again
this.removeEventListener(Event.ENTER_FRAME, aniEventListener); //remove our animation event listener
this.addEventListener(Events.FLAR_DETECTED, flarDetected); //re-start the FLAR_DETECTED event listener
}

These functions should be pretty self-expalnatory, and are called from our MainDisplay class, like so:

this.dispatchEvent(new Event(Events.SCENE_ANIMATED_IN)); //intro animation is done, dispatch the SCENE_ANIMATED_IN event into the event flow
this.dispatchEvent(new Event(Events.SCENE_ANIMATED_OUT)); //ending animation is done, dispatch the SCENE_ANIMATED_OUT event into the event flow

By structuring your project like this, you can keep all of your PV3D objects and animations in one file and all of your FLAR detection and listeners in another. They are simply linked together via event listeners. I hope this helps you with your next project. Happy coding!

About Curious Minds
We are a web development firm in New York and Chicago, providing development resources and consulting for websites and mobile apps since 2004.