うさがにっき

読書感想文とプログラムのこと書いてきます

ViroCoreの使い方、samplecode解説

概要

  • ViroMedia, ViroCoreの解説
  • ViroCoreを使ってみる
  • ViroCoreのsamplecodeを読んで何をしているか読み解く

詳細

ViroMediaとは

  • UnityやUnreal Engineを使わないMobileNative環境でAR/VR開発をするためのライブラリを作っている、利用料無料(2018/3/2現在)
    viromedia.com

  • 主なライブラリは以下の二つ

    • ViroReact

      • React NativeでAR/VRが簡単に開発できるライブラリ
        viromedia.com
    • ViroCore

      • Android NativeでAR/VR開発が簡単に開発できるライブラリ、XCodeのSceneKitのようにAndroidでAR/VR開発ができるようになる
        viromedia.com

ViroCoreとは

  • 今回はViroCoreを使ってARCoreを使ってみる
  • 上述の通り、ViroCoreではSceneKitのようにAndroidでAR/VR開発ができるようになるというのがポイントだが、ViroMedia曰く他にも以下のようなメリットがあるらしい
    • Android Platform(ARCore/Cardboard/GearVR/Daydream) Support
    • INTUITIVE JAVA DEVELOPMENT(SceneKitっぽく書ける)
    • POWERFUL RENDERER
    • PHYSICS ENGINE
    • DOCUMENTATION(確かにドキュメントはしっかりしている感じ)

とりあえずビルド、動かす

virocore.viromedia.com

            <meta-data
            android:name="com.viromedia.API_KEY"
            android:value="APIKEYを取得して入力" />

別のSampleを動かす

  • 上記のGetting-startedのものはARCore感がないのでもうちょっとちゃんとしたSampleを試してみる
    github.com

  • 「ARHelloWorldAndroid」プロジェクトをimport、上記と同じようにAPIKEYを設定すれば動くはず、もし動かない場合はBuild > Select Build Variants...からarcoreDebugを選択
    f:id:tiro105:20180302174247g:plain

Projectを眺めてViroCore雰囲気を掴む

Project Package構成

f:id:tiro105:20180302174914p:plain:w500

  • src/main/java/com.example.virosample下のソースがメイン
  • 3D Model(objとかtextureとか)はasset下に配置
  • gvr用ARCore用でAndroidManifest.xmlがある(productFlavorsで管理している)
  • BuildVariantでgvrDebugとかを選べば同じプロジェクトからGVR用のapkを出力できる、だがこのプロジェクトではARCore用のコードしかないのでGVRを選んでも真っ黒な画面が表示されるだけ

ViroHelper.java

  • assetから画像(obj用のtexture)を取得するメソッドのみ

ViroActivity.java

  • BaseActivity的に使っているクラス、実際の処理はこのActivityを継承して記述する
  • productFravorsを見て、適切なViewをActivityに配置
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (BuildConfig.VIRO_PLATFORM.equalsIgnoreCase("GVR")) {
            mViroView = createGVRView();

        } else if (BuildConfig.VIRO_PLATFORM.equalsIgnoreCase("OVR")) {
            mViroView = createOVRView();

        } else if (BuildConfig.VIRO_PLATFORM.equalsIgnoreCase("Scene")) {
            mViroView = createViroViewScene();
        } else if (BuildConfig.VIRO_PLATFORM.equalsIgnoreCase("ARCore")) {
            mViroView = createViroARCoreScene();
        }
        setContentView(mViroView);
    }
  • 今回はARCoreなのでcreateViroARCoreScene()で作ったViewが使われる
    private ViroView createViroARCoreScene() {
        RendererConfiguration config = new RendererConfiguration();
        config.setShadowsEnabled(true);
        config.setBloomEnabled(true);
        config.setHDREnabled(true);
        config.setPBREnabled(true);

        // ViroViewARCore
        // https://developer.viromedia.com/virocore/reference/com/viro/core/ViroViewARCore.html
        // RendererConfigurationを指定することができる
        ViroViewARCore viroView = new ViroViewARCore(this, new ViroViewARCore.StartupListener() {
            @Override
            public void onSuccess() {
                onRendererStart();
            }

            @Override
            public void onFailure(ViroViewARCore.StartupError error, String errorMessage) {
                onRendererFailed(error.toString(), errorMessage);
            }
        }, config);
        return viroView;
    }
  • なおonRendererStart()とonRendererFailed()はこのクラスで空で実装されており、継承クラスで実装する
    // 子クラスで実装してほしいらしい
    public void onRendererStart() {
        // Override this function to start building your scene here!
    }

    // 子クラスで実装してほしいらしい
    public void onRendererFailed(String error, String errorMessage) {
        // Fail as you wish!
    }

ViroARHelloWorldActivity.java

  • メインの処理を行っているクラス、ここにSceneKitっぽくいろいろ書いてある
  • onRendererStart()の実装
    • ARSceneを作ってViewに設定している
    • ARSceneに対してARScene.Listenerを実装したTrackedPlanesControllerを設定
    • anchorから平面認識して、planeの描画などはTrackedPlanesControllerが担当
    /**
     * Create an AR scene that tracks planes. Clicking on a plane places a 3D Object on that spot.
     */
    @Override
    public void onRendererStart() {
        // Create the 3d ar scene, and display the point clouds.
        mScene = new ARScene();
        mScene.displayPointCloud(true);

        // Create an TrackedPlanesController to visually display tracked planes
        TrackedPlanesController controller = new TrackedPlanesController(this, mViroView);

        // Spawn a 3D Droid on the position where the user has clicked on a tracked plane.
        // plane click時の処理はcontrollerに委譲
        controller.addOnPlaneClickListener(new ClickListener() {
            @Override
            public void onClick(int i, Node node, Vector clickPosition) {
                // Droidくん配置
                createDroidAtPosition(clickPosition);
            }

            @Override
            public void onClickState(int i, Node node, ClickState clickState, Vector vector) {
                //No-op
            }
        });

        mScene.setListener(controller);
        mViroView.setScene(mScene);
    }
  • TrackedPlanesControllerの実装
    • 平面を認識してplaneを作成して配置するなどを行う
    • onAnchorFound()でarAnchor.getType()がplaneなら平面と認識していろいろ処理をしている
    • 以下のドキュメントにもある通り、ARScene.Listenerを実装していることによりARSceneの各イベント時の挙動を定義できる
      ARScene.Listener | Android Developers
    /**
     * An TrackedPlanesController that tracks planes and renders a surface on them.
     */
    private static class TrackedPlanesController implements ARScene.Listener {
        // WeakReference
        // https://qiita.com/yyyske/items/daa5c844647604e27e4f
        // why does this code use WeakReference
        // http://ishiitakeru-programing-memo.blogspot.jp/2015/10/android-weakreference.html
        private WeakReference<Activity> mCurrentActivityWeak;
        private boolean searchingForPlanesLayoutIsVisible = false;
        private HashMap<String, Node> surfaces = new HashMap<String, Node>();
        private Set<ClickListener> mPlaneClickListeners = new HashSet<ClickListener>();

        public TrackedPlanesController(Activity activity, View rootView){
            mCurrentActivityWeak = new WeakReference<Activity>(activity);

            // Inflate viro_view_hud.xml layout to display a "Searching for surfaces" text view.
            View.inflate(activity, R.layout.viro_view_hud, ((ViewGroup) rootView));
        }

        /**
         * Register click listener for other components to listen for click events that occur
         * on tracked planes. In this example, a listener is registered during scene creation,
         * so as spawn 3d droids on a click.
         */
        public void addOnPlaneClickListener(ClickListener listener){
            mPlaneClickListeners.add(listener);
        }

        public void removeOnPlaneClickListener(ClickListener listener){
            if (mPlaneClickListeners.contains(listener)){
                mPlaneClickListeners.remove(listener);
            }
        }

        /**
         * Once a Tracked plane is found, we can hide the our "Searching for Surfaces" UI.
         */
        private void hideIsTrackingLayoutUI(){
            if (searchingForPlanesLayoutIsVisible){
                return;
            }
            searchingForPlanesLayoutIsVisible = true;

            Activity activity = mCurrentActivityWeak.get();
            if (activity == null){
                return;
            }

            View isTrackingFrameLayout = activity.findViewById(R.id.viro_view_hud);
            isTrackingFrameLayout.animate().alpha(0.0f).setDuration(2000);
        }

        @Override
        public void onAnchorFound(ARAnchor arAnchor, ARNode arNode) {
            // Spawn a visual plane if a PlaneAnchor was found
            if (arAnchor.getType() == ARAnchor.Type.PLANE){
                ARPlaneAnchor planeAnchor = (ARPlaneAnchor)arAnchor;

                // Create the visual geometry representing this plane
                Vector dimensions = planeAnchor.getExtent();
                Surface plane = new Surface(1,1);
                plane.setWidth(dimensions.x);
                plane.setHeight(dimensions.z);

                // Set a default material for this plane.
                Material material = new Material();
                material.setDiffuseColor(Color.parseColor("#BF000000"));
                plane.setMaterials(Arrays.asList(material));

                // Attach it to the node
                Node planeNode = new Node();
                planeNode.setGeometry(plane);
                planeNode.setRotation(new Vector(-Math.toRadians(90.0), 0, 0));
                planeNode.setPosition(planeAnchor.getCenter());

                // Attach this planeNode to the anchor's arNode
                arNode.addChildNode(planeNode);
                surfaces.put(arAnchor.getAnchorId(), planeNode);

                // Attach click listeners to be notified upon a plane onClick.
                planeNode.setClickListener(new ClickListener() {
                    @Override
                    public void onClick(int i, Node node, Vector vector) {
                        for (ClickListener listener : mPlaneClickListeners){
                            listener.onClick(i, node, vector);
                        }
                    }

                    @Override
                    public void onClickState(int i, Node node, ClickState clickState, Vector vector) {
                        //No-op
                    }
                });

                // Finally, hide isTracking UI if we haven't done so already.
                hideIsTrackingLayoutUI();
            }
        }

        @Override
        public void onAnchorUpdated(ARAnchor arAnchor, ARNode arNode) {
            if (arAnchor.getType() == ARAnchor.Type.PLANE){
                ARPlaneAnchor planeAnchor = (ARPlaneAnchor)arAnchor;

                // Update the mesh surface geometry
                Node node = surfaces.get(arAnchor.getAnchorId());
                Surface plane = (Surface) node.getGeometry();
                Vector dimensions = planeAnchor.getExtent();
                plane.setWidth(dimensions.x);
                plane.setHeight(dimensions.z);
            }
        }

        @Override
        public void onAnchorRemoved(ARAnchor arAnchor, ARNode arNode) {
            surfaces.remove(arAnchor.getAnchorId());
        }

        @Override
        public void onTrackingInitialized() {
            //No-op
        }

        @Override
        public void onAmbientLightUpdate(float v, float v1) {
            //No-op
        }
    }
  • ちなみにobjファイルロードして、texture貼るのはこんな感じ
        object3D.loadModel(Uri.parse("file:///android_asset/andy.obj"), Object3D.Type.OBJ, new AsyncObject3DListener() {
            @Override
            public void onObject3DLoaded(final Object3D object, final Object3D.Type type) {
                // When the model is loaded, set the texture associated with this OBJ.
                Texture objectTexture = new Texture(bot, Texture.Format.RGBA8, false, false);
                Material material = new Material();
                material.setDiffuseTexture(objectTexture);
                object3D.getGeometry().setMaterials(Arrays.asList(material));
            }

            @Override
            public void onObject3DFailed(String s) {
            }
        });

感想

  • OpenGLESと比べてはるかに簡単に記述できる、Unity, SceneKitを触ったことがある人なら概念は理解しやすいと感じる
  • NativeでのARCoreではRajawaliを検討していたのだが、ARCore対応のIssueはでているもののまだ取り込まれていない github.com
  • また最近Rajawaliが動いてない感じなので期待している github.com
  • スター数はViroReactが126、ViroCoreが26なのでいまのところかなりマイナーな感じは否めない
  • ARCoreを触りたい、かつSceneKitを触ったことがある人は実はあまりいないのか・・・?

参考