FLAR how-to: Multiple instances of multiple markers.

kingofthetinycubes

UPDATE: We’re be remiss not to also point you to Eric’s excellent FLARManager class. He took some of the stuff we’ve covered here and put a far more delicious topping on it.

One of the big things we’ve been wrasslin’ with recently here at Squidder is how to handle multiple instances of multiple markers using FLARToolKit. Well we haven’t totally nailed it — close, but there are still a few niggling issues. So we’re looking to you, dear Squiddite, to help us out.

Before reading on, you can get our source code here.

It contains all the libraries (FLARToolKit, Papervision, even some of our own) you need to get going, even if this your first FLAR project. It’s as simple as extending a single class, creating an array of the markers you want to load in, and rocking and rolling. It also works great even if you’re only dealing with a single symbol.

Read on for more thrilling details!

Anyway, what we’ve done is created our own version of FLARMultiMakerDetector that tries to maintain a consistent array of all the markers on the screen. So when a new marker is added, FLARSquidderMarkerDetector fires off a MARKER_ADDED event, which contains the id your marker (the order in which was loaded in — so the first marker has an id of 0) and the index (first marker on the screen, second, etc).  Conversely, when an object is removed, a MARKER_REMOVED event is fired. So this brings things a bit more in line with the type of event driven actionscript that we’re used to writing.

The problem that we haven’t quite gotten our brains around is how to make sure that we always have the correct index when multiple instances of the same marker is added. Here’s an example: You have three instances of Marker A on the screen. How do you know which one specifically is the one removed?

Currently, this isn’t a visible issue, since the other two markers will just “snap” instantly into place. But try tweening an object to its next location and you’ll see things start to flip flop between symbols.

Once again, feel free to grab our source code and let us know what you think!

Category: as3, labs, source code

Tagged: , ,

35 Responses

  1. Ryan says:

    Wikkid….the future is yours…I’ll have to check this out as soon as I can.

  2. [...] a post explaining their [...]

    [WORDPRESS HASHCASH] The comment’s server IP (208.66.71.77) doesn’t match the comment’s URL host IP (208.66.71.78) and so is spam.

  3. technodai says:

    Thanks for this guys, you’ve really helped me get my head round things. off to generate some of my own markers now and see what fun ensues!

  4. Javier Patiño says:

    This only works with usb cam?

  5. Javier Patiño says:

    I forget…thank you nice work

  6. Jon says:

    Hey Javier – This should work fine with any webcam supported by flash. Indeed, we also tested with a MiniDV camera plugged in via Firewire and it worked wonderfully.

  7. ericsoco says:

    hey squidder folks!
    excellent work you’ve done here. i was also working on a small framework to make using FLARToolkit easier; was almost done a couple days ago, but then came across the work you did and was inspired to make some improvements.

    FLARManager is now available:
    http://words.transmote.com/?p=10

    it decouples FLARToolkit from Papervision3D, so you can use FLARToolkit for things totally non-pv3d related (though you can still use it with pv3d!); it can track multiple instances of multiple patterns; and a bunch of other good stuff.

    please check it out!

  8. Jon says:

    Eric – Brilliant! Nice move to add the onMarkerUpdated event. Everything else looks fantastically well put together. Nice job!

  9. Javier Patiño says:

    Hey jon.. I need you help… I just try to implement video texture in my test application but dont work I use VideoStreamMaterial; command but the video sound and dissapear automatic.
    I dont know what happend… If you want help me this is the code…thanks you in advance…

    package {
    import flash.events.Event;
    import flash.media.Video;
    import flash.net.NetConnection;
    import flash.net.NetStream;
    import flash.events.MouseEvent;
    import org.papervision3d.materials.WireframeMaterial;
    import org.papervision3d.objects.parsers.Collada;
    import org.papervision3d.objects.primitives.Plane;
    import org.papervision3d.materials.VideoStreamMaterial;
    import org.papervision3d.objects.DisplayObject3D;
    //import org.papervision3d.cameras.Camera3D;
    //import org.papervision3d.render.LazyRenderEngine;
    //import org.papervision3d.scenes.Scene3D;
    //import org.papervision3d.view.Viewport3D;
    //import org.papervision3d.objects.parsers.MD2; //————————

    [SWF(width=640,height=480,frameRate=30,backgroundColor=0x0)]

    public class RASuite extends PV3DARApp
    {
    private var mCollada:Collada;
    //private var mD2:MD2; //———————————
    private var universe:DisplayObject3D;

    private var plane:Plane;
    private var _plane:Plane;
    private var videoCristina:VideoStreamMaterial;
    private var quality:uint = 8;
    private var nc:NetConnection;
    private var video:Video;
    private var ns:NetStream;

    public function RASuite()
    {
    this.addEventListener(Event.INIT, this.onInit);
    this.init(’Data/camera_para.dat’, ‘Data/patt.hiro’);

    //plane = new Plane(videoStreamMaterial, 1600, 1200, quality, quality);
    //plane = new Plane(videoStreamMaterial, 320, 240, quality, quality);

    var customClient:Object = new Object();
    customClient.onMetaData = metaDataHandler;
    nc = new NetConnection();
    nc.connect(null);
    ns = new NetStream(nc);
    ns.client = customClient;
    ns.play(”cristina.flv”);

    video = new Video();
    video.width = 320;
    video.height = 240;
    video.smoothing = true;
    video.attachNetStream(ns);
    }

    protected override function onInit():void
    {
    super.onInit();
    this.removeEventListener(Event.INIT, this.onInit);

    //Plano en lineas ————————————————–
    var wmat:WireframeMaterial = new WireframeMaterial(0xff0000, 1, 2);
    wmat.doubleSided = true;
    this._plane = new Plane(wmat, 80, 80);
    this._baseNode.addChild(this._plane);
    //——————————————————————

    //Carga Objeto DAE ————————–
    //this.mCollada = new Collada(”tank.dae”);
    //this._baseNode.addChild(this.mCollada);
    //——————————————-

    //——————————————————————
    videoCristina = new VideoStreamMaterial(video, ns);
    this.plane = new Plane(videoCristina, 80, 80, quality, quality);
    this._baseNode.addChild(this.plane);
    //_scene.addChild(plane);
    //this._camera3d.target = this.plane;
    //this._renderer.render();

    //——————————————————————

    //this.mD2 = new MD2(”tris.md2″);
    this.universe = new DisplayObject3D();
    this._baseNode.addChild(this.universe);

    //llama acciones externas—————————————-
    this.stage.addEventListener(MouseEvent.CLICK, this._onClick);
    addEventListener( Event.ENTER_FRAME, loop3D );
    //—————————————————————
    }

    private function metaDataHandler(infoObject:Object):void
    {
    trace(’metaDataHandler’,infoObject);
    }

    private function _onClick(e:MouseEvent):void
    {
    //this.mirror = !this.mirror;
    this.ns.play(”cristina.flv”);
    }

    private function loop3D(event:Event):void
    {

    //_plane.x += ( ( -mouseX * 5 ) – _plane.x ) / 15;
    //_plane.y += ( ( -mouseY * 5 ) – _plane.y ) / 15;

    //this.ns.play(”cristina.flv”);
    }
    }
    }

  10. Tyler says:

    Thanks for the source code. It really helps accelerate my learning curve on this stuff. Keep up the great work!!

  11. Javier Patiño says:

    ericsoco excellent work…I have one question… this dont work with Flash IDE?
    1084: Syntax error: expecting identifier before lessthan.

  12. ericsoco says:

    @javier — it is not written to work with the flash IDE, but if you want to port it, please feel free. the source includes a Flex Builder project.

    apologies that comments aren’t working on my site (none of my internal links are!) — working through the wordpress pains as we speak….

  13. Francis says:

    Hello,
    thank you for this helpful source.
    I try to use Plane with movieclip instead cube, but it doesn’t work. I really don’t understand why, because markers are detected but I can’t see the movieclip…
    if somebody can help me, it would be fantastic.
    thank you

    this is my code:

    package {

    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.filters.*;
    import org.papervision3d.lights.PointLight3D;
    import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
    import org.papervision3d.materials.WireframeMaterial;
    import org.papervision3d.materials.utils.MaterialsList;
    import org.papervision3d.materials.MovieAssetMaterial;
    import org.papervision3d.objects.DisplayObject3D;
    //import org.papervision3d.objects.primitives.Cube;
    import org.papervision3d.objects.primitives.Plane;
    import com.squidder.flar.FLARMarkerObj;
    import com.squidder.flar.PVFLARBaseApplication;
    import com.squidder.flar.events.FLARDetectorEvent;

    /**
    * @author Jon Reiling
    */

    public class MultiFLARExample extends PVFLARBaseApplication {

    private var _planes : Array;
    public function MultiFLARExample() {

    _planes = new Array();
    _markers = new Array();
    _markers.push( new FLARMarkerObj( “assets/flar/crash.pat” , 16 , 50 , 80 ) );
    _markers.push( new FLARMarkerObj( “assets/flar/kickdrum.pat” , 16 , 50 , 80 ) );
    _markers.push( new FLARMarkerObj( “assets/flar/ride.pat” , 16 , 50 , 80 ) );
    _markers.push( new FLARMarkerObj( “assets/flar/snare.pat” , 16 , 50 , 80 ) );

    super( );
    }

    override protected function _detectMarkers() : void { _resultsArray = _flarDetector.updateMarkerPosition( _flarRaster , 80 , .5 );

    for ( var i : int = 0 ; i < _resultsArray.length ; i ++ ) {

    var subResults : Array = _resultsArray[ i ];

    for ( var j : * in subResults ) {

    _flarDetector.getTransmationMatrix( subResults[ j ], _resultMat );
    if ( _planes[ i ][ j ] != null ) transformMatrix( _planes[ i ][ j ] , _resultMat );
    }
    }
    }
    override protected function _handleMarkerAdded( event : FLARDetectorEvent ) : void {

    _addPlane( event.codeId , event.codeIndex );
    }

    override protected function _handleMarkerRemove( event : FLARDetectorEvent ) : void {

    _removePlane( event.codeId , event.codeIndex );

    }

    private function _addPlane( id:int , index:int ) : void {

    if ( _planes[ id ] == null ) _planes[ id ] = new Array();

    if ( _planes[ id ][ index ] == null ) {

    var movieAssetMaterial = _getFlatMaterial( id );
    movieAssetMaterial.animated = true;
    movieAssetMaterial.doubleSided = true;
    var dispObj : DisplayObject3D = new DisplayObject3D();
    var plane : Plane = new Plane( movieAssetMaterial, 80 , 80 );
    dispObj.addChild( plane );
    _planes[ id ][ index ] = plane;
    }
    //_baseNode.addChild( _planes[ id ][ index ] );

    }
    private function _removePlane( id:int , index:int ) : void {
    if ( _planes[ id ] == null ) _planes[ id ] = new Array();
    if ( _planes[ id ][ index ] != null ) {
    _baseNode.removeChild( _planes[ id ][ index ] );
    }
    }
    private function _getFlatMaterial( id:int ) : MovieAssetMaterial {

    if ( id == 0 ) {
    return new MovieAssetMaterial( “myMaterial”, false );
    } else if ( id == 1 ){
    return new MovieAssetMaterial( “myMaterial2″, false );
    } else if ( id == 2 ) {
    return new MovieAssetMaterial( “myMaterial3″, false );
    } else {
    return new MovieAssetMaterial( “myMaterial4″, false );
    }
    }
    }
    }

  14. Javier Patiño says:

    why didnt call each markers independent?

  15. gesundheit says:

    Sehr wertvolle Informationen! Empfehlen!

    [WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.

  16. fussball says:

    Gute Arbeit hier! Gute Inhalte.

    [WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.

  17. [...] squidder.com: A bunch of great stuff, including HOWTO with multiple markers and source code! These guys seem to be posting stuff every day, keep an eye on them! [...]

  18. Stijn Debacker says:

    first of all Thanks for this tutorial and markermanager, it helped me allot. The Idea of the drummachine? => Genious!

    I have a little question, mayby you can help me. I managed to flip the image of my webcam horizontally so my screen looks like a normal mirror. But when I move my markers, the cubes will move to the opposite direction??? Have you got any idea how i can fix this? I’ve been searching in the core files of Flar and stuff, but I can’t seem to find the right files or lines of code that I need to change!

  19. Stijn Debacker says:

    I found my solution! :-)
    for mirroring the image of the webcam:
    in FlarBaseApplication.as change the following
    _flarBitmap.scaleX = (_flarBitmap.scaleY = 2)*-1;
    _flarBitmap.x = 640;

    Put some ()’s arround the scaleY thingy and mirror it using *-1. To see the image move the image, in this case 640 pixels on the x-axe!

    Test the movie, you will see that you will be looking at a normal mirror but that, when if you move your marker it moves in the opposite direction!
    Solution:
    In the file PVFLARBaseApplication.as

    at the bottom of the file you will find a roster of variables! it looks like this:
    m.n11 = r.m01; m.n12 = r.m00; m.n13 = r.m02; m.n14 = r.m03;
    m.n21 = -r.m11; m.n22 = -r.m10; m.n23 = -r.m12; m.n24 = -r.m13;
    m.n31 = r.m21; m.n32 = r.m20; m.n33 = r.m22; m.n34 = r.m23;

    Change it into:
    m.n11 = -r.m00; m.n12 = -r.m01; m.n13 = -r.m02; m.n14 = -r.m03;
    m.n21 = -r.m10; m.n22 = -r.m11; m.n23 = -r.m12; m.n24 = -r.m13;
    m.n31 = r.m20; m.n32 = r.m21; m.n33 = r.m22; m.n34 = r.m23;

    And everything will work :)

  20. Javier Patiño says:

    public class FLARTest extends PVFLARBaseApplication
    {
    public var cube1:DisplayObject3D;
    public var cube0:DisplayObject3D;
    private var _cubes:Array;

    public function FLARTest()
    {
    _cubes = new Array();
    return;
    }// end function

    public function tweenTransformMatrix(param1, param2:FLARTransMatResult) : void
    {
    var _loc_3:Matrix3D;
    _loc_3 = param1.transform;
    TweenLite.to(_loc_3, 0, {n11:param2.m01, n12:param2.m00, n13:param2.m02, n14:param2.m03, n21:-param2.m11, n22:-param2.m10, n23:-param2.m12, n24:-param2.m13, n31:param2.m21, n32:param2.m20, n33:param2.m22, n34:param2.m23});
    return;
    }// end function

    private function _removeCube(param1:int, param2:int) : void
    {
    if (_cubes[param1] == null)
    {
    _cubes[param1] = new Array();
    }// end if
    if (_cubes[param1][param2] != null)
    {
    _baseNode.removeChild(_cubes[param1][param2]);
    }// end if
    return;
    }// end function

    private function _removeSquare(param1:FLARDetectorEvent) : void
    {
    _removeCube(param1.codeId, param1.codeIndex);
    SoundManager.getInstance().playSound(String(param1.codeId));
    return;
    }// end function

    private function _addCube(param1:int, param2:int) : DisplayObject3D
    {
    var _loc_3:PointLight3D;
    var _loc_4:FlatShadeMaterial;
    var _loc_5:DisplayObject3D;
    var _loc_6:Cube;
    if (_cubes[param1] == null)
    {
    _cubes[param1] = new Array();
    }// end if
    if (_cubes[param1][param2] == null)
    {
    _loc_3 = new PointLight3D();
    _loc_3.y = 1000;
    _loc_3.z = -1000;
    if (param1 == 0)
    {
    _loc_4 = new FlatShadeMaterial(_loc_3, 16720554, 7671886);
    }
    else if (param1 == 1)
    {
    _loc_4 = new FlatShadeMaterial(_loc_3, 65280, 1127185);
    }
    else if (param1 == 2)
    {
    _loc_4 = new FlatShadeMaterial(_loc_3, 255, 1118515);
    }
    else if (param1 == 3)
    {
    _loc_4 = new FlatShadeMaterial(_loc_3, 7829367, 1118481);
    }// end else if
    _loc_5 = new DisplayObject3D();
    _loc_6 = new Cube(new MaterialsList({all:_loc_4}), 40, 40, 40);
    _loc_6.z = 20;
    _loc_5.addChild(_loc_6);
    _baseNode.addChild(_loc_5);
    _cubes[param1][param2] = _loc_5;
    return _loc_5;
    }
    else
    {
    _baseNode.addChild(_cubes[param1][param2]);
    }// end else if
    return _cubes[param1][param2];
    }// end function

    override protected function _onRenderTick(param1:Event = null) : void
    {
    var _loc_2:Array;
    var _loc_3:int;
    var _loc_4:Array;
    var _loc_5:*;
    _flarBitmap.bitmapData.draw(_video);
    _loc_2 = _flarDetector.updateMarkerPosition(_flarRaster, 80, 0.5);
    _loc_3 = 0;
    while (_loc_3 < _loc_2.length)
    {
    // label
    _loc_4 = _loc_2[_loc_3];
    for (_loc_5 in _loc_4)
    {
    // label
    _flarDetector.getTransmationMatrix(_loc_4[_loc_5], _resultMat);
    if (_cubes[_loc_3][_loc_5] != null)
    {
    tweenTransformMatrix(_cubes[_loc_3][_loc_5], _resultMat);
    }// end if
    }// end of for … in
    _loc_3++;
    }// end while
    renderer.renderScene(scene, _camera, viewport);
    return;
    }// end function

    private function _addSquare(param1:FLARDetectorEvent) : void
    {
    _addCube(param1.codeId, param1.codeIndex);
    return;
    }// end function

    override protected function _init(param1:BaseApplicationEvent) : void
    {
    super._init(param1);
    _flarDetector.addEventListener(FLARDetectorEvent.SQUARE_ADDED, _addSquare);
    _flarDetector.addEventListener(FLARDetectorEvent.SQUARE_REMOVED, _removeSquare);
    SoundManager.getInstance().addSound(BaseConfiguration.loader.getAsset(”Sound0″), “0″);
    SoundManager.getInstance().addSound(BaseConfiguration.loader.getAsset(”Sound1″), “1″);
    SoundManager.getInstance().addSound(BaseConfiguration.loader.getAsset(”Sound2″), “2″);
    SoundManager.getInstance().addSound(BaseConfiguration.loader.getAsset(”Sound3″), “3″);
    return;
    }// end function

    }
    }

  21. technodai says:

    Ok I must admit, I’m confused. I’ve looked at your entry on creating custom markers and tried it myself. Unfortunately the markers are not recognised. How have you generated the markers for your multimarker example? At first I thought that they were 4×4 patterns but then realised this is not the case. The markers included with the package do not accurately translate into 4×4, 8×8, 16×16 or 32×32 as far as I can see. What am I missing?

  22. technodai says:

    I’ve since discovered that the online marker generator at tarotaro doesn’t work correctly when you upload an image (at least not when I try). When saving a file from a webcam it works fine. I am getting instances when FLARToolkit get confused between two markers that are really quite different though…

  23. Cristian says:

    Hi Jon, how can I insert collada in your code? (I m use Flash CS4)

  24. me says:

    How can I put collada models instead of cubes. Can somebody help me?

  25. Jw says:

    Nice work you have here =), i have a little question of my own
    i am currently trying to detect when the two objects i have are colliding, but somehow it keeps returning this error

    TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at MultiFLARExample/::_update()

    This is the code , it would be great if you could help me thanks!!

    private function _update(e:Event):void {

    if(objectsOnScreen>=2){
    if(_baseNode.getChildByName(_objs[0][0]).getChildByName(”_dae”).hitTestObject( _baseNode.getChildByName(_objs[0][0]).getChildByName(”_dae”),10)){
    _dae2.rotationZ+=10;
    }
    }

    if(keyDown_UpArrow == true){
    _dae.y += 16;
    }
    if(keyDown_DownArrow == true){
    _dae.y -= 16;
    }
    if(keyDown_LeftArrow == true){
    _dae.x += 16;
    }
    if(keyDown_RightArrow == true){
    _dae.x -= 16;
    }

    break;
    }

  26. Jw says:

    opps sorry it should be this

    private function _update(e:Event):void {

    if(objectsOnScreen>=2){
    if(_baseNode.getChildByName(_objs[0][0]).getChildByName(”_dae”).hitTestObject( _baseNode.getChildByName(_objs[1][0]).getChildByName(”_dae2”),10)){
    _dae2.rotationZ+=10;
    }
    }

    if(keyDown_UpArrow == true){
    _dae.y += 16;
    }
    if(keyDown_DownArrow == true){
    _dae.y -= 16;
    }
    if(keyDown_LeftArrow == true){
    _dae.x += 16;
    }
    if(keyDown_RightArrow == true){
    _dae.x -= 16;
    }

    break;
    }

  27. S says:

    I’m really new to this AR stuff, I managed to get this stuff working: http://gotoandlearn.com/play?id=105

    when I try this multimarker stuff, it runs, but it won’t show any feed from my webcam… my webcams light doesnt even turn on (and no, webcam isnt broken ^^ )

    help? what part of the code is responsible for the webcam?

  28. S says:

    omg, since I moved a few files around all I had to do was change the FLARBaseApplication.as

  29. GRACE says:

    Thank you for the coding. I’ve been digesting the script, and pretty much understand the process.

    Is there a FLAR marker “locked-on” similar to the USPS box simulator?

  30. shachar oz says:

    hi guys,
    love your work. i am trying to use your code to make a multi marker environment with planes instead of cubes. i have had great success with working with the cubes.
    from some reason any plane i try to add to the _baseNode is being disapeared a moment after.
    my guess is that it is something to do with the detectMarkers() function. can you please explain what does it do?

  31. shachar oz says:

    how can i load a squidder multiple marker based app onto a webpage?
    i have succeeded doing so on my personal PC, but i wish to see it also on MAC. i have been able to see your FLARDrum app on the MAC.
    tried downloading the JS you used, but dont know what’s wrong. any idea?

  32. mat says:

    Hey was just wondering how and where i would enter the code to make the drum app you made?
    i just wanna have a shot at making different sounds, should be fun

    thanks

  33. Terry Meehan says:

    I’m new to the AR thing and I have need of some basic code.
    I’ve tried several tutorials and downloaded other beginning projects.
    I can get these to work as they are intended but am having trouble
    adapting them to my needs.
    I think my needs would be pretty basic for a reasonably experienced
    person and I would be willing to even pay someone $50.00 through
    Paypal for their help.

    Basically what I need, in a hurry, is a .fla and .swf file that will allow someone to sample the AR experience
    through the web, like the GE site.
    I would need it set up to work with up to 3 different .dae-format
    models and 3 different markers.
    If I can only get 1 that may be okay too.
    I would need it to work with whatever model or marker file I added and I know they would have to have the same names as the files they are replacing.

    I don’t know if this makes a difference but the models may sometimes
    be larger than the markers, i.e., a marker held to the forehead may
    show a 3d mask that covers the face.

    In other words, I need a reaady-to-go web page to display various models on a web page.
    Please contact me at meehan@esrock.com
    If this just modified code you aready have, that is fine.
    Thanks.

  34. Longfellow says:

    hi Terry Meehan,
    maybe you can try this web page I build up before.

    http://www.asn.com.tw/flex/PV3DFlar/MarkerGeneratorOnline.html

    it can load .dae models from somewhere your .dae file placed and generate a new marker for that model online immediately.

  35. Anthony says:

    hey guys, check out http://www.DaeVision.com

    Augmented Reality for everyone

Leave a Reply

Powered by WP Hashcash

Spam Protection by WP-SpamFree

Twitter