[AIR3.0] IN2ARを体験するのだ! (4)

そうだ! IN2ARを体験してみよう! :bouzu:

IN2AR

IN2AR SDK を使ってみるよ。 :boy:  Worker も使ってみちゃったりして。 :bouzu:

:caution: 要 Flash Player 11.8 以上

This movie requires Flash Player 11.8.0

Main.as
package {

  import flash.display.Sprite;
  import flash.display.StageScaleMode;
  import flash.display.StageAlign;
  import flash.system.System;
  import flash.events.Event;
  import flash.geom.Vector3D;
  import flash.media.Camera;
  import flash.media.Video;
  import flash.display.BitmapData;
  import flash.geom.Matrix;
  import flash.utils.ByteArray;
  import flash.display.BlendMode;
  import flash.geom.ColorTransform;

  import flash.system.Worker;
  import flash.system.WorkerDomain;
  import flash.system.MessageChannel;
  import flash.concurrent.Mutex;

  import in2ar.controls.CaptureController;
  import in2ar.away3d.textures.CaptureTexture;
  import in2ar.away3d.containers.IN2ARContainer3D;
  import in2ar.away3d.cameras.lenses.IN2ARCameraLens;

  import in2ar.worker.IN2ARWorker;
  import in2ar.worker.IN2ARWorkerMessage;

  import away3d.Away3D;
  import away3d.containers.View3D;
  import away3d.containers.Scene3D;
  import away3d.cameras.Camera3D;
  import away3d.lights.DirectionalLight;
  import away3d.containers.ObjectContainer3D;
  import away3d.entities.Mesh;
  import away3d.materials.TextureMaterial;
  import away3d.materials.lightpickers.StaticLightPicker;
  import away3d.loaders.Loader3D;
  import away3d.loaders.parsers.DAEParser;
  import away3d.events.LoaderEvent;
  import away3d.loaders.misc.AssetLoaderContext;
  import away3d.library.AssetLibrary;
  import away3d.library.assets.AssetType;
  import away3d.events.AssetEvent;
  import away3d.animators.ParticleAnimationSet;
  import away3d.animators.ParticleAnimator;
  import away3d.animators.data.ParticlePropertiesMode;
  import away3d.animators.data.ParticleProperties;
  import away3d.animators.nodes.ParticleBillboardNode;
  import away3d.animators.nodes.ParticleVelocityNode;
  import away3d.animators.nodes.ParticleFollowNode;
  import away3d.animators.nodes.ParticleColorNode;
  import away3d.core.base.ParticleGeometry;
  import away3d.core.base.Geometry;
  import away3d.primitives.PlaneGeometry;
  import away3d.core.base.Object3D;
  import away3d.tools.helpers.ParticleGeometryHelper;
  import away3d.utils.Cast;

  import org.libspark.betweenas3.BetweenAS3;
  import org.libspark.betweenas3.tweens.ITween;
  import org.libspark.betweenas3.events.TweenEvent;
  import org.libspark.betweenas3.easing.*;


  [SWF(backgroundColor="#333333", width="640", height="368", frameRate="60")]

  public class Main extends Sprite {
    [Embed(source="assets/negimiku/negimiku.dae", mimeType="application/octet-stream")]
    private var ColladaData:Class;
    [Embed(source="assets/negimiku/negimiku.png")]
    private var ImageData:Class;
    [Embed(source="assets/particle.png")]
    private var Particle:Class;
    // プロパティ
    private var worker:Worker;
    private var backWorker:IN2ARWorker;
    private var mainToBack:MessageChannel;
    private var backToMain:MessageChannel;
    private var mutex:Mutex;
    private var bytes:ByteArray;
    private var view:View3D;
    private var scene:Scene3D;
    private var camera:Camera3D;
    private var light:DirectionalLight;
    private var negimiku:ObjectContainer3D;
    private var hand:ObjectContainer3D;
    private var arm:Mesh;
    private var negi:Mesh;
    private var animation:ParticleAnimationSet;
    private var animator1:ParticleAnimator;
    private var animator2:ParticleAnimator;
    private var particle1:Mesh;
    private var particle2:Mesh;
    private var leader1:Object3D;
    private var leader2:Object3D;
    private var angle1:Number = 90;
    private var angle2:Number = 90;
    private static var radian:Number = Math.PI/180;
    private static var center:Vector3D = new Vector3D();
    private var bw:uint = 640;
    private var bh:uint = 368;
    private var offset:uint;
    private var fx:Number = 320;
    private var fy:Number = 184;
    private var lens:IN2ARCameraLens;
    private var container:IN2ARContainer3D;
    private var video:Video;
    private var controller:CaptureController;
    private var rect:Rectangle;
    private var capture:CaptureTexture;
    private static const MIRROR:Boolean = false;

    // コンストラクタ
    public function Main() {
      stage.scaleMode = StageScaleMode.NO_SCALE;
      stage.align = StageAlign.TOP_LEFT;
      System.pauseForGCIfCollectionImminent(1);
      //init();
      if (Worker.current.isPrimordial) {
        worker = WorkerDomain.current.createWorker(loaderInfo.bytes);
        mainToBack = Worker.current.createMessageChannel(worker);
        backToMain = worker.createMessageChannel(Worker.current);
        backToMain.addEventListener(Event.CHANNEL_MESSAGE, msgBackToMain);
        worker.setSharedProperty("mainToBack", mainToBack);
        worker.setSharedProperty("backToMain", backToMain);
        mutex = new Mutex();
        worker.setSharedProperty("mutex", mutex);
        offset = bw*bh*4;
        bytes = new ByteArray();
        bytes.length = offset + (1024 << 3);
        bytes.shareable = true;
        worker.setSharedProperty("bytes", bytes);
        worker.start();
      } else {
        backWorker = new IN2ARWorker();
      }

    }

    // メソッド
    private function msgBackToMain(evt:Event):void {
      var id:int = backToMain.receive();
      switch (id) {
        case IN2ARWorkerMessage.IN2AR_INIT :
          init();
          break;
        case IN2ARWorkerMessage.IN2AR_GET_INTRINSIC :
          fx = backToMain.receive();
          fy = backToMain.receive();

          if (lens) lens.updateProjection(fx, fy, bw, bh, bw, bh);
          break;
        case IN2ARWorkerMessage.IN2AR_DETECTION_DETECTED :
          detect();
          break;
        case IN2ARWorkerMessage.IN2AR_DETECTION_FAILED :
          fail();
          break;
      }
    }
    private function init():void {
      var sw:uint = stage.fullScreenWidth;
      var sh:uint = stage.fullScreenHeight;
      var scale:Number = sh/sw;
      bh = bw*scale;
      mainToBack.send(IN2ARWorkerMessage.IN2AR_GET_INTRINSIC);
      var cam:Camera = Camera.getCamera();
      cam.setMode(bw, bh, 24, false);

      video = new Video(bw, bh);
      video.attachCamera(cam);

      controller = new CaptureController(cam, CaptureController.UPDATE_BITMAPDATA);
      rect = new Rectangle(0, 0, bw, bh);
      view = new View3D();
      scene = view.scene;
      camera = view.camera;

      addChild(view);
      light = new DirectionalLight();
      scene.addChild(light);

      setup();
      initialize();
      resize();
      stage.addEventListener(Event.RESIZE, resize, false, 0, true);
      stage.mouseChildren = false;
    }
    private function setup():void {
      view.backgroundColor = 0x000000;
      view.antiAlias = 4;
      capture = new CaptureTexture(controller.bitmapData);
      capture.mirror = MIRROR;

      view.background = capture;
      lens = new IN2ARCameraLens();
      lens.updateProjection(fx, fy, bw, bh, bw, bh);

      camera.lens = lens;
      camera.position = center;

      light.direction = new Vector3D(0, -1, 1);
      light.ambient = 0.3;
      light.diffuse = 1;
      light.specular = 1;

    }
    private function initialize():void {
      //Parsers.enableAllBundled();
      var context:AssetLoaderContext = new AssetLoaderContext();
      context.mapUrlToData("negimiku.png", new ImageData());

      AssetLibrary.addEventListener(AssetEvent.ASSET_COMPLETE, loaded, false, 0, true);
      var loader:Loader3D = new Loader3D();
      loader.addEventListener(LoaderEvent.RESOURCE_COMPLETE, complete, false, 0, true);
      loader.loadData(new ColladaData(), context, null, new DAEParser());


      var plane:Geometry = new PlaneGeometry(30, 30, 1, 1, false);
      var planes:Vector.<Geometry> = new Vector.<Geometry>();
      for (var n:uint = 0; n < 400; n++) {
        planes.push(plane);
      }

      var geometry:ParticleGeometry = ParticleGeometryHelper.generateGeometry(planes);
      var material:TextureMaterial = new TextureMaterial(Cast.bitmapTexture(Particle));
      material.blendMode = BlendMode.ADD;
      particle1 = new Mesh(geometry, material);
      particle1.z = 150;
      particle2 = new Mesh(geometry, material);
      particle2.z = 150;
      var startColorTrans:ColorTransform = new ColorTransform(0.36, 0.54, 1, 1);
      var endColorTrans:ColorTransform = new ColorTransform(0.36, 0.54, 1, 0);

      animation = new ParticleAnimationSet(true, true, true);
      animation.addAnimation(new ParticleBillboardNode());
      animation.addAnimation(new ParticleVelocityNode(ParticlePropertiesMode.LOCAL_STATIC));
      var followNode:ParticleFollowNode = new ParticleFollowNode(true, false);
      animation.addAnimation(followNode);
      animation.addAnimation(new ParticleColorNode(ParticlePropertiesMode.GLOBAL, true, false, false, false, startColorTrans, endColorTrans));
      animation.initParticleFunc = initParticle;

      leader1 = new Object3D();
      leader2 = new Object3D();

      animator1 = new ParticleAnimator(animation);
      particle1.animator = animator1;
      animator1.start();
      followNode.getAnimationState(animator1).followTarget = leader1;

      animator2 = new ParticleAnimator(animation);
      particle2.animator = animator2;
      animator2.start();
      followNode.getAnimationState(animator2).followTarget = leader2;

    }
    private function loaded(evt:AssetEvent):void {
      if (evt.asset.assetType == AssetType.CONTAINER) {
        negimiku = ObjectContainer3D(evt.asset);
        negimiku.rotationX = 180;
        if (MIRROR) negimiku.rotationZ = 180;
        negimiku.scale(8.4);

      }
      if (evt.asset.assetType == AssetType.MATERIAL) {
        var material:TextureMaterial = TextureMaterial(evt.asset);
        var lightPicker:StaticLightPicker = new StaticLightPicker([light]);
        material.lightPicker = lightPicker;

        material.ambientColor = 0xFFFFFF;
        //material.alpha = 0.99;
        material.alpha = 1;
        //material.alphaThreshold = 0.5;
        material.alphaThreshold = 0.9;
      }
      if (evt.asset.assetType == AssetType.MESH) {
        switch (evt.asset.name) {
          case "R_arm" :
            arm = Mesh(evt.asset);
            break;
          case "negi" :
            negi = Mesh(evt.asset);
            break;
        }
      }
    }
    private function complete(evt:LoaderEvent):void {
      AssetLibrary.removeEventListener(AssetEvent.ASSET_COMPLETE, loaded);
      evt.target.removeEventListener(LoaderEvent.RESOURCE_COMPLETE, complete);
      container = new IN2ARContainer3D();
      scene.addChild(container);

      container.addChild(negimiku);
      hand = new ObjectContainer3D();
      negimiku.addChild(hand);
      hand.addChild(arm);
      hand.addChild(negi);

      container.addChild(particle1);
      container.addChild(particle2);

      addEventListener(Event.ENTER_FRAME, render, false, 0, true);
      jump();
      swingHand();
      controller.addEventListener(Event.VIDEO_FRAME, update);
      //lib.addListener(IN2ARDetectionEvent.DETECTED, detected);
      //lib.addListener(IN2ARDetectionEvent.FAILED, failed);

    }
    private function jump():void {
      var itween:ITween = BetweenAS3.serial(
        BetweenAS3.tween(negimiku, {z: 200}, {z: 100}, 0.5, Cubic.easeOut),
        BetweenAS3.tween(negimiku, {z: 100}, {z: 200}, 0.5, Cubic.easeIn)
      );
      itween.addEventListener(TweenEvent.COMPLETE, jumped, false, 0, true);
      itween.play();

    }
    private function jumped(evt:TweenEvent):void {
      evt.target.removeEventListener(TweenEvent.COMPLETE, jumped);
      jump();
    }
    private function swingHand():void {
      var itween:ITween = BetweenAS3.serial(
        BetweenAS3.tween(hand, {rotationX: -40}, {rotationX: 0}, 0.15, Quad.easeOut),
        BetweenAS3.tween(hand, {rotationX: 0}, {rotationX: -40}, 0.15, Quad.easeIn),
        BetweenAS3.tween(hand, {rotationX: 20}, {rotationX: 0}, 0.1, Quad.easeOut),
        BetweenAS3.tween(hand, {rotationX: 0}, {rotationX: 20}, 0.1, Quad.easeIn)
      );
      itween.addEventListener(TweenEvent.COMPLETE, swingedHand, false, 0, true);
      itween.play();

    }
    private function swingedHand(evt:TweenEvent):void {
      evt.target.removeEventListener(TweenEvent.COMPLETE, swingedHand);
      swingHand();
    }
    private function initParticle(prop:ParticleProperties):void {
      prop.startTime = Math.random()*4;
      prop.duration = 4;

      prop[ParticleVelocityNode.VELOCITY_VECTOR3D] = new Vector3D((Math.random() – 0.5)*100, (Math.random() – 2)*100, (Math.random() – 0.5)*100);
    }
    private function render(evt:Event):void {
      angle1 -= 5;
      angle2 += 5;
      leader1.x = 100*Math.cos(angle1*radian);
      leader1.y = 100*Math.sin(angle1*radian);
      leader2.x = 100*Math.cos(angle2*radian);
      leader2.y = 100*Math.sin(angle2*radian);
      //camera.lookAt(center);
      view.render();
    }
    private function update(evt:Event):void {
      mutex.lock();
      bytes.position = 0;
      bitmapData.copyPixelsToByteArray(rect, bytes);
      mutex.unlock();
      mainToBack.send(IN2ARWorkerMessage.IN2AR_ADD_FRAME);
      capture.invalidate();
    }
    private function detect():void {
      mutex.lock();
      var info:ByteArray = bytes;
      info.position = offset;

      var rotationMatrix:Vector.<Number> = new Vector.<Number>(9);
      var translationVector:Vector.<Number> = new Vector.<Number>(3);

      var max:int = info.readInt();
      for(var n:uint = 0; n < max; n++) {
        var referenceId:int = info.readInt();
        var state:String = (info.readInt() == 0) ? "detected" : "tracked";
        for (var t:uint = 0; t < 9; t++) {
          rotationMatrix[t] = info.readDouble();
        }
        translationVector[0] = info.readDouble();
        translationVector[1] = info.readDouble();
        translationVector[2] = info.readDouble();

        if (container) {
          container.in2arTransform(rotationMatrix, translationVector, 0.8, MIRROR);
        }
      }
      mutex.unlock();
    }
    private function fail():void {
      if (container) {
        container.lost();
      }
    }
    private function resize(evt:Event = null):void {
      //var sw:uint = stage.stageWidth;
      //var sh:uint = stage.stageHeight;

      var sw:uint = stage.fullScreenWidth;
      var sh:uint = stage.fullScreenHeight;
      view.width = sw;
      view.height = sh;
    }

  }

}

IN2ARWorker.as (in2ar.worker.IN2ARWorker.as)
package in2ar.worker {

  import flash.display.Sprite;
  import flash.events.Event;
  import flash.system.LoaderContext;
  import flash.display.BitmapData;
  import flash.geom.Rectangle;
  import flash.utils.ByteArray;

  import flash.system.Worker;
  import flash.system.MessageChannel;
  import flash.concurrent.Mutex;

  import com.in2ar.IN2AR;
  import com.in2ar.IIN2AR;
  import com.in2ar.calibration.IntrinsicParameters;
  import com.in2ar.event.IN2ARDetectionEvent;
  import com.in2ar.detect.IN2ARReference;
  import com.in2ar.detect.IN2ARDetectType;

  import in2ar.worker.IN2ARWorkerMessage;

  [SWF(backgroundColor="#FFFFFF", frameRate="1")]

  public class IN2ARWorker extends Sprite {
    [Embed(source="../../../assets/def_data.ass", mimeType="application/octet-stream")]
    private var DefinitionData:Class;
    // プロパティ
    private var shell:IN2AR;
    private var lib:IIN2AR;
    private var intrinsic:IntrinsicParameters;
    private var mainToBack:MessageChannel;
    private var backToMain:MessageChannel;
    private var mutex:Mutex;
    private var bytes:ByteArray;
    private static var bw:uint = 640;
    private static var bh:uint = 368;
    private var buffer:BitmapData;
    private var rect:Rectangle;
    private var offset:int;

    // コンストラクタ
    public function IN2ARWorker() {
      if (Worker.current.isPrimordial) return;
      var worker:Worker = Worker.current;
      mainToBack = worker.getSharedProperty("mainToBack");
      backToMain = worker.getSharedProperty("backToMain");
      mutex = worker.getSharedProperty("mutex");
      bytes = worker.getSharedProperty("bytes");

      mainToBack.addEventListener(Event.CHANNEL_MESSAGE, msgMainToBack);
      shell = new IN2AR(new LoaderContext(false));
      shell.addEventListener(Event.INIT, init, false, 0, true);

    }

    // メソッド
    private function init(evt:Event):void {
      shell.removeEventListener(Event.INIT, init);
      lib = shell.lib;
      lib.init(bw, bh, 300, 2, 100, stage);
      lib.setupIndexing(12, 10, false);
      lib.setUseLSHDictionary(true);
      lib.addReferenceObject(ByteArray(new DefinitionData1()));
      lib.addReferenceObject(ByteArray(new DefinitionData2()));
      lib.setMaxReferencesPerFrame(2);

      buffer = new BitmapData(bw, bh, false, 0x00000000);
      rect = new Rectangle(0, 0, bw, bh);
      offset = bw*bh*4;
      backToMain.send(IN2ARWorkerMessage.IN2AR_INIT);
      addEventListener(Event.ENTER_FRAME, update, false, 0, true);
      lib.addListener(IN2ARDetectionEvent.DETECTED, detected);
      lib.addListener(IN2ARDetectionEvent.FAILED, failed);

    }
    private function msgMainToBack(evt:Event):void {
      var id:int = mainToBack.receive();
      switch (id) {
        case IN2ARWorkerMessage.IN2AR_ADD_FRAME :
          mutex.lock();
          bytes.position = 0;
          buffer.setPixels(rect, bytes);
          mutex.unlock();
          lib.addBitmapFrame(buffer);
          break;
        case IN2ARWorkerMessage.IN2AR_GET_INTRINSIC :
          backToMain.send(IN2ARWorkerMessage.IN2AR_GET_INTRINSIC);
          backToMain.send(intrinsic.fx);
          backToMain.send(intrinsic.fy);

          break;
        case IN2ARWorkerMessage.IN2AR_SET_INTRINSIC :
          var fx:Number = mainToBack.receive();
          var fy:Number = mainToBack.receive();
          var cx:Number = mainToBack.receive();
          var cy:Number = mainToBack.receive();

          intrinsic.update(fx, fy, cx, cy);
          lib.updateIntrinsicParams();
          break;
        case IN2ARWorkerMessage.IN2AR_START :
          removeEventListener(Event.ENTER_FRAME, update);
          addEventListener(Event.ENTER_FRAME, update, false, 0, true);
          break;
        case IN2ARWorkerMessage.IN2AR_STOP :
          removeEventListener(Event.ENTER_FRAME, update);
          break;
        case IN2ARWorkerMessage.IN2AR_DISPOSE :
          removeEventListener(Event.ENTER_FRAME, update);
          lib.removeListener(IN2ARDetectionEvent.DETECTED, detected);
          lib.removeListener(IN2ARDetectionEvent.FAILED, failed);
          dispose();
          break;
      }
    }
    private function detected(evt:IN2ARDetectionEvent):void {
      var references:Vector.<IN2ARReference> = evt.detectedReferences;
      var max:int = evt.detectedReferencesCount;

      mutex.lock();
      bytes.position = offset;
      bytes.writeInt(max);

      for (var n:uint = 0; n < max; n++) {
        var reference:IN2ARReference = references[n];
        var state:String = reference.detectType;
        bytes.writeInt(reference.id);
        bytes.writeInt(state == IN2ARDetectType.DETECTED ? 0 : 1);
        for (var t:uint = 0; t < 9; t++) {
          bytes.writeDouble(reference.rotationMatrix[t]);
        }
        bytes.writeDouble(reference.translationVector[0]);
        bytes.writeDouble(reference.translationVector[1]);
        bytes.writeDouble(reference.translationVector[2]);

      }
      mutex.unlock();
      backToMain.send(IN2ARWorkerMessage.IN2AR_DETECTION_DETECTED);
    }
    private function failed(evt:IN2ARDetectionEvent):void {
      backToMain.send(IN2ARWorkerMessage.IN2AR_DETECTION_FAILED);
    }
    private function update(evt:Event):void {
      lib.run();
    }
    public function dispose():void {
      shell.destroy();
      lib = null;
      shell = null;
    }

  }

}

CaptureController.as (in2ar.controls.CaptureController.as)
(サンプルコードの CaptureController.as を参考に作成)
CaptureTexture.as (in2ar.away3d.textures.CaptureTexture.as)
(サンプルコードの com.in2ar.away3d.Away3DCaptureTexture.as を参考に作成)
IN2ARContainer3D.as (in2ar.away3d.containers.IN2ARContainer3D.as)
(サンプルコードの com.in2ar.away3d.Away3DContainer.as を参考に作成)
IN2ARCameraLens.as (in2ar.away3d.cameras.lenses.IN2ARCameraLens.as)
(サンプルコードの com.in2ar.away3d.Away3DCameraLens.as を参考に作成)
IN2ARWorkerMessage.as (in2ar.worker.IN2ARWorkerMessage.as)
(サンプルコードの com.in2ar.worker.IN2ARWorkerMessage.as を参考に作成)

libsフォルダに、in2ar.ane を用意。ライブラリパスを通す。

CCなら、Android端末にデプロイするだけ。(AIR 4.0/ASC 2.0)


[Nexus7]

in2ar_android_worker1

in2ar_android_worker2

in2ar_android_worker3