インスタントトラッキング

以下のセクションでは、Wikitude JavaScript SDKのInstantTracking機能について簡単な例を紹介し、それに基づいて2つの成功体験を示します。 最初の例は最小限の実装であり、これによってWikitude JavaScript SDKが提供するシンプルさを示しています。 その後でさらに、3Dモデルの補強と予備的な相互作用を追加し、最終的に本格的なユースケースのサンプルに拡張していきます。

個々のセクションで無駄な重複や余分な実装をしなくてもいいように、サンプルを通して作業することをお勧めします。

SMART - シームレスなARトラッキング

SMARTは、ARKit、ARCore、WikitudeのSLAMエンジンをあらゆるデバイスに対応する単一の拡張現実SDK(クロスプラットフォーム)に統合したシームレスなAPIです。 SMARTはiOSデバイスの92.6%、市場で入手可能なAndroidデバイスの約35%をカバーし、多くのデバイスに最適なAR体験を提供します。

WikitudeでARCoreを有効にするには、Enable ARCoreセクションのARCoreのドキュメントの指示に従います。これはサンプルアプリでも確認することができます。

SMARTはデフォルトで有効になっていますが、AR.InstantTrackerを作成するときにsmartEnabledオプションを指定してパラメータを設定することで無効にすることができます。 このオプションは実行時に動作を変更することはできません。

new AR.InstantTracker({
    smartEnabled: false
});

デバイスがSMART(ARCoreを使用したトラッキング)でプラットフォームアシスタントトラッキングをサポートしているかどうかを確認するには、AR.hardware.smart.isPlatformAssistedTrackingSupportedを使用する必要があります。ARCoreには、必要なARCoreコンパニオンアプリを自動的にダウンロードしてインストールするメカニズムがあります。Wikitude JavaScript SDKを使用してこの手順を開始するには、AR.hardware.smart.isPlatformAssistedTrackingSupportedを呼び出し、該当する場合はSMARTフラグをtrueに設定してAR.InstantTrackerを作成する2段階のプロセスです。最初の呼び出しでは、現在のデバイスがARCoreをサポートしているかどうかを判断するための連続クエリーが開始されます。 AR.hardware.smart.onPlatformAssistedTrackingAvailabilityChangedコールバック関数を使用して現在のステータスを繰り返し報告します。このコールバックは、次の列挙定数の1つを入力パラメータとして提供します。

value action
AR.hardware.smart.SmartAvailability.INDETERMINATE_QUERY_FAILED 何らかの理由でクエリが失敗しました。 再試行するか、ARCoreなしで実行するトラッカーを作成してください。 コールバックは再び呼び出されません。
AR.hardware.smart.SmartAvailability.CHECKING_QUERY_ONGOING クエリは現在進行中です。 何もする必要はありません。 コールバックが再度呼び出されます。
AR.hardware.smart.SmartAvailability.UNSUPPORTED デバイスはARCoreをサポートしていません。 ARCoreなしで実行するトラッカーを作成します。 コールバックは再び呼び出されません。
AR.hardware.smart.SmartAvailability.SUPPORTED_UPDATE_REQUIRED デバイスはARCoreをサポートしていますが、コンパニオンアプリをインストールまたは更新する必要があります。 インストールプロセスを開始するトラッカーを作成します。 コールバックが再度呼び出されます。
AR.hardware.smart.SmartAvailability.SUPPORTED デバイスはARCoreをサポートしており、コンパニオンの現在のバージョンはすでにインストールされています。 ARCoreで実行するトラッカーを作成します。 コールバックは再び呼び出されません。

JavaScriptコードに移された場合、表には次のスニペットが表示されます。

AR.hardware.smart.onPlatformAssistedTrackingAvailabilityChanged = function(availability) {
    switch(availability) {
        case AR.hardware.smart.SmartAvailability.INDETERMINATE_QUERY_FAILED:
            /* query failed for some reason; try again or accept the fact. */
            World.showUserInstructions("Could not determine if platform assisted tracking is supported.<br>Running without platform assisted tracking (ARKit or ARCore).");
            World.createOverlays();
            break;
        case AR.hardware.smart.SmartAvailability.CHECKING_QUERY_ONGOING:
            /* query currently ongoing; be patient and do nothing or inform the user about the ongoing process */
            break;
        case AR.hardware.smart.SmartAvailability.UNSUPPORTED:
            /* not supported, create the scene now without platform assisted tracking enabled */
            World.showUserInstructions("Running without platform assisted tracking (ARKit or ARCore).");
            World.createOverlays();
            break;
        case AR.hardware.smart.SmartAvailability.SUPPORTED_UPDATE_REQUIRED:
        case AR.hardware.smart.SmartAvailability.SUPPORTED:
            /* supported, create the scene now with platform assisted tracking enabled
             *
             * SUPPORTED_UPDATE_REQUIRED may be followed by SUPPORTED, make sure not to
             * create the scene twice
             */
            World.platformAssisstedTrackingSupported = true;
            if (!World.createOverlaysCalled) {
                World.showUserInstructions("Running with platform assisted tracking(ARKit or ARCore). <br> Move your phone around until the crosshair turns green, which is when you can start tracking.");
                World.createOverlays();
                World.createOverlaysCalled = true;
            }
            break;
    }
};

ARCoreアプリケーションのインストールが必要な場合は、トラッカーを2回作成しないように注意してください。 AR.hardware.smart.SmartAvailability.SUPPORTED_UPDATE_REQUIRED定数の後にAR.hardware.smart.SmartAvailability.SUPPORTED定数が続く場合がありますが、一度だけトラッカーを作成したい場合があります。 提示されたスニペットは、その参照をチェックすることによってトラッカーを二重に作成することを回避します。

SMARTは、制御を犠牲にして改善されたトラッキング機能を提供します。 そのため、SMARTを有効にしてプラットフォームアシスタントトラッキング機能を使用すると一部のWikitude SDK機能を使用できません。

機能 SMART ON(プラットフォームアシスタントトラッキングをサポート) SMART OFF
トラッキングの改善 x
平面の方向 x
カメラの制御 x
インスタントターゲットの保存と読み込み x
平面検出 x

紹介

インスタントトラッキングは、以前にWikitude SDKで導入されたものとは異なり、あらかじめ定義されたターゲットを認識し、トラッキングを開始することを目的とせず、ただちに任意の環境でトラッキングを開始するアルゴリズムです。 これにより、非常に具体的なユースケースを実装することができます。 そのようなユースケースの1つとして、家具製品の視覚化アプリケーションを最終的な例として実装しています。

アルゴリズムは2つの異なる状態で動作します。 最初のものは初期化状態です。 この状態では、ユーザは、単にデバイスを指示し、それによってインジケータを整列させることによって、トラッキングの起点を定義する必要があります。 一旦、(ユーザが積極的に確認する必要がある)ユーザが満足できるものであると判明すると、トラッキング状態への移行が実行されます。 この状態では、環境は継続的にトラッキングされているため、シーン内に拡張を配置することができます。

プラットフォームアシスタントトラッキングが有効な場合、前提条件として平面が決定されている必要があるため、初期状態からトラッキング状態への切り替えが常に可能であるとは限りません。平面が決定されるまでには数秒かかることがあります。基礎になるアルゴリズムが準備されていない状態で切り替えようとすると、エラーコールバックによってエラーが発生します。

初期状態

トラッキング状態

インスタントトラッキングのアルゴリズムは、初期化状態で別の入力値を提供することを要求します。 具体的には、トラッキングしているデバイスの高さは、シーンの範囲内で正確に増加したスケールを調整するために必要となります。 この目的のために、3つの例は、メートル単位で高さを設定できる範囲入力要素を備えています。

プラットフォームアシスタントトラッキングが有効な場合、平面の入力パラメータより上のデバイスの高さと、対応範囲の入力要素は、初期状態のスケールにのみ影響します。トラッキング状態のスケールは、プラットフォーム追跡アルゴリズムによって自動的に決定されます。スケール値が正しくない場合、ステータスの切替時にスケールの不一致を引き起こすので、正しい値を設定することを推奨します。

初期化中にインスタントトラッキングの平面を整列するための別のパラメータを設定できます。この平面を初期化インジケータにして表示させ、床の代わりに壁をターゲットとしてトラッキングするために回転させることができます。詳細については、APIリファレンスを参照してください。

プラットフォームアシスタントトラッキングが有効な場合、トラッキングする平面の方向を変更することができません。ただし、エラーコールバックでエラーを発生させる以外の影響はありません。

基本的なインスタントトラッキング

基本的なインスタントトラッキングの例は、インスタントトラッキングのアルゴリズムの最小限の実装を示します。 これは2つの必須クラスAR.InstantTrackerAR.InstantTrackableを使用しています。 既に画像のトラッキングについて精通しているならば、AR.ImageTrackerAR.ImageTrackableは画像のトラッキングと同じパターンを使用するため、使い慣れているはずです。

AR.InstantTrackerは、パラメータなしでインスタンス化できます。

this.tracker = new AR.InstantTracker();

初期高さ(deviceHeight)を指定できるほか、状態間の遷移(onChangedState)が発生したときに呼び出されるコールバック関数を実装できます。

this.tracker = new AR.InstantTracker({
    onChangedState:  function onChangedStateFn(state) {
    },
    deviceHeight: 1.0
});

AR.InstantTrackableは、初期に生成されたトラッキングのインスタンスでインスタンス化することはできますが、実用的な使用の場合は、初期化状態とトラッキング状態の両方でレンダリングされるdrawablesを使用することを推奨します。そうすることで、2つのAR.ImageDrawableインスタンスとそれに対応する2つのAR.ImageResourceインスタンスが生成され、同じように提供されます。

var crossHairsRedImage = new AR.ImageResource("assets/crosshairs_red.png");
this.crossHairsRedDrawable = new AR.ImageDrawable(crossHairsRedImage, 1.0);
var crossHairsBlueImage = new AR.ImageResource("assets/crosshairs_blue.png");
this.crossHairsBlueDrawable = new AR.ImageDrawable(crossHairsBlueImage, 1.0);
this.instantTrackable = new AR.InstantTrackable(this.tracker, {
    drawables: {
        cam: World.crossHairsBlueDrawable,
        initialization: World.crossHairsRedDrawable
    },
});

SMARTによってプラットフォームアシスタントトラッキングがサポートされている場合、ARKitまたはARCoreが平面を検出した後にトラッキングを開始することができます。このため、AR.InstantTracker.canStartTrackingで確認して、初期状態からトラッキングに切り換えが可能なことを示すために、3つ目のAR.ImageDrawableを追加することを推奨します。

var crossHairsGreenImage = new AR.ImageResource("assets/crosshairs_green.png");
this.crossHairsGreenDrawable = new AR.ImageDrawable(crossHairsGreenImage, 1.0);

setInterval(
    function() {
        if (World.tracker.canStartTracking) {
            World.instantTrackable.drawables.initialization = [World.crossHairsGreenDrawable];
        } else {
            World.instantTrackable.drawables.initialization = [World.crossHairsRedDrawable];
        }
    },
    1000
);

追加で必要となる変更は、ある状態から別の状態に変わる際にどのように移行するかについてです。 ここでは、ボタンクリックで便利に呼び出すchangeTrackerState関数を実装します。 AR.InstantTrackerStateは、各状態を識別するために使用される2つの値を定義します。

changeTrackerState: function changeTrackerStateFn() {
    if (this.tracker.state === AR.InstantTrackerState.INITIALIZING) {
        this.tracker.state = AR.InstantTrackerState.TRACKING;
    } else {
        this.tracker.state = AR.InstantTrackerState.INITIALIZING;
    }
}
<input id="tracking-start-stop-button" type="image" src="assets/buttons/start.png" onclick="World.changeTrackerState()"/>

最後に、changeTrackingHeight関数を用いて、AR.InstantTrackerdeviceHeightプロパティを設定し、範囲入力エレメントに接続します。 この変更は厳密に言えば必須ではありませんが、この方法を用いて、正確にデバイスの高さを指定することを強く推奨します。

changeTrackingHeight: function changeTrackingHeightFn(height) {
    this.tracker.deviceHeight = parseFloat(height);
}
<input id="tracking-height-slider" type="range" min="0.1" value="1.0" max="2.0" step="0.1" onchange="World.changeTrackingHeight(value)">

このセクションで概説した例では、初期状態をインジケータにして赤い十字線画像の表示を行い、その増補としてトラッキング状態のときに対応する青の十字画像を表示します。 この例は非常に些細なものですが、読者にすばやくトラッキングの基本概念を理解させる目的があります。 さらに、サンプルアプリケーションの単純さを強調しておきます。 JavaScriptコードの行数が20行に満たない場合、このサンプルは完璧に機能します。 以下のセクションで紹介する変更は、同じように単純です。

基本的なインスタントトラッキングの初期状態

基本的なインスタントトラッキングのトラッキング状態

平面上の3Dモデル

このセクションでは、以前に実装されたサンプルアプリケーションを修正して、ユーザーとのやりとりや、より洗練された機能拡張の使用方法を示します。

まず、画面上をクリックするだけで、シーン内に配置する3Dモデルを追加してアプリケーションを拡張します。 内部的には、このタッチで定義された線は、インスタントトラッキングの平面で交差し、モデルのtransformプロパティに適用される交差位置を生成します。 この発生時に、AR.InstantTrackableonTrackingPlaneClickコールバックが呼び出され、交差位置座標が別々のパラメータとして提供されます。

this.instantTrackable = new AR.InstantTrackable(this.tracker, {
    drawables: {
        cam: crossHairsBlueDrawable,
        initialization: crossHairsRedDrawable
    },
    onTrackingPlaneClick: function onTrackingPlaneClickFn(xpos, ypos) {
        World.addModel(xpos, ypos);
    }
});

addModel関数は、AR.Modelをインスタンス化し、初期スケール、変換、回転(rotate)のプロパティを設定します。 translateプロパティは、onTrackingPlaneClickコールバックに渡される交差座標に直接設定されます。 視覚的多様性を追加するために、Z軸回りの回転がランダム化されます。

addModel: function addModelFn(xpos, ypos) {
    if (World.isTracking()) {
        var model = new AR.Model("assets/models/couch.wt3", {
            scale: {
                x: 0.045,
                y: 0.045,
                z: 0.045
            },
            translate: {
                x: xpos,
                y: ypos
            },
            rotate: {
                z: Math.random() * 360.0
            },
        })
        allCurrentModels.push(model);
        this.instantTrackable.drawables.addCamDrawable(model);
    }
}

isTracking関数は、トラッカーがトラッキング状態になっているかどうかをチェックして、ユーザーの相互作用を制限します。

isTracking: function isTrackingFn() {
    return (this.tracker.state === AR.InstantTrackerState.TRACKING);
}

この例は、生成されたモデルをリセットする機能をさらに含み、インスタントトラッキングに直接関係しないので省略します。

空のシーン

いくつかクリック入力後のシーン

インタラクティブ性

最後に、サンプルアプリケーションをさらに拡張して、いくつかの異なるAR.Modelを配置し、以前に配置された拡張を変更できるジェスチャを導入します。

まずこのページの最初のペアの画像に表示しているように、使用可能なモデルごとに1つずつボタンをいくつか追加します。 そのHTML定義は簡単なためここでは省略しますが、将来参照するため、その存在を認識しておく必要があります。 さらに重要なのは、最初に、それぞれのボタンでtouchstartイベントがトリガーされるようにイベントリスナーを設定している点です。これを使ってrequestedModelプロパティを設定します。 このプロパティは、インスタンス化するモデルを示します。

setupEventListeners: function setupEventListenersFn() {
    document.getElementById("tracking-model-button-clock").addEventListener('touchstart', function(ev){
        World.requestedModel = 0;
    }, false);
    document.getElementById("tracking-model-button-couch").addEventListener('touchstart', function(ev){
        World.requestedModel = 1;
    }, false);
    document.getElementById("tracking-model-button-chair").addEventListener('touchstart', function(ev){
        World.requestedModel = 2;
    }, false);
    document.getElementById("tracking-model-button-table").addEventListener('touchstart', function(ev){
        World.requestedModel = 3;
    }, false);
    document.getElementById("tracking-model-button-trainer").addEventListener('touchstart', function(ev){
        World.requestedModel = 4;
    }, false);
},

モデルをインスタンス化するために、AR.InstantTrackableonTrackingPlaneDragChangedおよびonTrackingPlaneDragEndedコールバックをonTrackingPlaneDragBeganonTrackingPlaneDragChangedおよびonTrackingPlaneDragEndedコールバックを実装します。 開始および終了コールバックは、片方向のドラッグが開始または解除されたときに呼び出されます。 ジェスチャが継続されている限り、更新コールバックが定期的に呼び出されます。 onTrackingPlaneClickコールバックと同様に、それらはタッチレイとインスタントトラッキングプレーンの交差位置を取得します。

this.instantTrackable = new AR.InstantTrackable(this.tracker, {
    drawables: {
        cam: crossHairsBlueDrawable,
        initialization: crossHairsRedDrawable
    },
    onTrackingPlaneDragBegan: function onTrackingPlaneDragBeganFn(xPos, yPos) {
        World.updatePlaneDrag(xPos, yPos);
    },
    onTrackingPlaneDragChanged: function onTrackingPlaneDragChangedFn(xPos, yPos) {
        World.updatePlaneDrag(xPos, yPos);
    },
    onTrackingPlaneDragEnded: function onTrackingPlaneDragEndedFn(xPos, yPos) {
        World.updatePlaneDrag(xPos, yPos);
        World.initialDrag = false;
    }
});

交差位置をupdatePlaneDrag関数に転送するだけで、requestModelプロパティをチェックします。これは、以前ボタンのtouchmoveハンドラ関数で設定されている可能性があります。 その場合、addModel関数を使用して、指定された交差位置にそのIDのモデルが作成されます。 その後のonTrackingPlaneDragChanged呼び出しによって位置が更新され、以前作成したボタンからAR.Modelインスタンスをドラッグして作成できます。

addModel: function addModelFn(pathIndex, xpos, ypos) {
    if (World.isTracking()) {
        var modelIndex = rotationValues.length;
        World.addModelValues();
        var model = new AR.Model(World.modelPaths[pathIndex], {
            scale: {
                x: defaultScaleValue,
                y: defaultScaleValue,
                z: defaultScaleValue
            },
            translate: {
                x: xpos,
                y: ypos
            },
            onDragChanged: function(relativeX, relativeY, intersectionX, intersectionY) {
                this.translate = {x:intersectionX, y:intersectionY};
            },
            onRotationChanged: function(angleInDegrees) {
                this.rotate.z = rotationValues[modelIndex] - angleInDegrees;
            },
            onRotationEnded: function(angleInDegrees) {
               rotationValues[modelIndex] = this.rotate.z
            },
            onScaleChanged: function(scale) {
                var scaleValue = scaleValues[modelIndex] * scale;
                this.scale = {x: scaleValue, y: scaleValue, z: scaleValue};
            },
            onScaleEnded: function(scale) {
                scaleValues[modelIndex] = this.scale.x;
            }
        })
        allCurrentModels.push(model);
        lastAddedModel = model;
        this.instantTrackable.drawables.addCamDrawable(model);
    }
}

addModel関数は前の例で示したものと非常によく似ていますが、いくつかの追加が行われています。特に、ジェスチャーコールバックがAR.Modelに追加されました。これらのコールバックは、onClickコールバックの呼び出しパターンに従います。 AR.Drawableから派生したクラスがタッチでヒットすると、対応するジェスチャーコールバックが実装されている場合に、そのジェスチャーコールバックが呼び出されます。トラッキングできない場合は、次にAR.Contextオブジェクトとみなされます。 AR.InstantTrackableに属するAR.Drawableの場合、ドラッグジェスチャコールバックは相対座標に加えてトラッキングのプレーンの交点座標を受け取ります。これにより、受信した交差座標にAR.Drawableのtranslateプロパティを設定するだけで、インスタントトラッキングシーンのオブジェクトを変換する手段が可能になります。個々のコンポーネントを設定するのではなく、JavaScriptからネイティブOS環境への必要なコールバックを最小限に抑えるために、変換プロパティ(回転、スケール、変換)全体を設定することをお勧めします。回転ジェスチャコールバックとスケールジェスチャコールバックは、関心のある読者が親切に言及した専用のジェスチャサンプルのとおりに動作します。

考慮すべきもう1つの複雑さは、対話的に動作する変換相互作用を防止するために、2つの指のジェスチャがアクティブである間にドラッグジェスチャを無効にすることです。 これは、AR.context.on2FingerGestureStartedコールバックを実装し、その中にフラグを設定することで実現できます。

AR.context.on2FingerGestureStarted = function() {
    oneFingerGestureAllowed = false;
}

onDragChangedコールバックは、このフラグを考慮し、許可されている場合にのみtranslateプロパティを更新するようになっています。

onDragChanged: function(relativeX, relativeY, intersectionX, intersectionY) {
    if (oneFingerGestureAllowed) {
        this.translate = {x:intersectionX, y:intersectionY};
    }
}

このフラグは、次回のonDragBeganコールバック呼び出しでリセットされ、再び有効になります。

onDragBegan: function(x, y) {
    oneFingerGestureAllowed = true;
}

説明した変更は、最終的に家具製品の視覚化アプリケーションの初期使用例を実装します。ここでも、サンプルアプリケーションの側面はカバーされていませんが、インスタントトラッキング機能には直接関係なく、サンプルアプリケーションのソースコードから簡単に理解できます。

Wikitude社のオフィスの床にあるミニチュアのリビングルームのシーン

シーンとのやりとり

インスタントトラッキングを使用することで、3Dポイントをポイントクラウドからクエリすることができます。このセクションは、対応するサンプルアプリケーションに基づいてこの機能を紹介します。

この機能を使用するために、シーン内に2Dモデルを配置する位置が必要となります。この位置を取得するためにAR.context.onScreenClickイベントを使用して、入力としてマウスがクリックされたときの座標を受け取る関数をアタッチします。受け取った座標は、変更されていないAR.InstantTrackableconvertScreenCoordinateToPointCloudCoordinate関数に供給することができます。この関数では、入力座標に加えて、クエリ完了時に呼び出される他の2つの関数も必要です。これらの関数は、クエリが成功した場合と失敗した場合の両方で呼び出されます。成功は入力座標に対して3D位置が見つかることを意味し、失敗は3D位置が見つからないことを意味します。成功した場合は、3D位置は3つの異なるパラメータで提供されます。これらのパラメータは、以下のAR.Circleのような任意のAR.Drawableの平行移動を直接設定するために使用できます。シーンとのやり取りは、トラッキング状態でのみ使用できます。

AR.context.onScreenClick = function(touchLocation) {
                if ( World.tracker.state === AR.InstantTrackerState.TRACKING ) {
                    World.instantTrackable.convertScreenCoordinateToPointCloudCoordinate(touchLocation.x, touchLocation.y, function(x, y, z) {
                        var circle = new AR.Circle(0.1, {
                            translate: {
                                x: x,
                                y: y,
                                z: z
                            },
                            style: {
                                fillColor: '#FF8C0A'
                            }
                        });
                        World.instantTrackable.drawables.addCamDrawable(circle);
                    }, function () {
                        alert('nothing hit. try selecting another scene location');
                    });
                } else {
                    alert('Scene information are only available during tracking. Please click the start button to start tracking');
                }
            }
            

サンプルを実行すると、スクリーンのタッチ操作でトラッキング状態に円拡張が配置されます。

シーンピッキング機能を使用してWikitude社のオフィスの床に生成されたオレンジ色の円

持続的なインスタントターゲット

インスタントターゲットの保存および読み込みの機能により、AR体験は複数のユーザーがデバイスやオペレーティングシステムを横切って持続的にアクセスできるようになります。 さらにインスタントターゲットを拡大することができます。このセクションでは、サンプルアプリケーションに基づいてこの機能を紹介します。 この機能は、プラットフォームによるトラッキングが有効な場合は使用できません。

インスタントターゲットの保存

インスタントターゲットを保存するには、アクティブ状態のInstantTrackerがトラッキング状態でなければならず、指定されたパスのディレクトリが存在する必要があります。

有効なパスを取得するにはAR.platform.sendJSONObjectarchitectView.callJavaScriptのプラットフォームを使用します。 この例では、インスタントターゲットとともに拡張機能を保存および読み込む方法を示しています。 これは、すべてのアプリのカスタム方法に最適に実装されているSDKの機能の一部ではありません。

saveCurrentInstantTarget: function () {
    var augmentations = [];

    allCurrentModels.forEach(function (model) {
        augmentations.push({
            uri: model.uri,
            translate: model.translate,
            rotate: model.rotate,
            scale: model.scale
        });
    });

    if (this.tracker.state === AR.InstantTrackerState.TRACKING) {
        AR.platform.sendJSONObject({
            action: "save_current_instant_target",
            augmentations: JSON.stringify(augmentations)
        });
    } else {
        alert("Save instant target is only available while tracking.")
    }
}

saveCurrentInstantTargetToUrlは、保存するsaveCurrentInstantTargetのパスとして使用されるurlでプラットフォームコードから呼び出されます。

saveCurrentInstantTargetToUrl: function (url) {
    this.tracker.saveCurrentInstantTarget(url, function () {
        alert("Saving was successful");
    }, function (error) {
        alert("Saving failed: " + error);
    })
}

インスタントターゲットの読み込み

インスタントターゲットを読み込むには、アクティブトラッカーと以前に保存したインスタントターゲットがなければなりません。

AR.platform.sendJSONObjectarchitectView.callJavaScriptプラットフォームから有効なパスを取得するために使用されます。

loadExistingInstantTarget: function () {
    AR.platform.sendJSONObject({
        action: "load_existing_instant_target"
    });
},

loadExistingInstantTargetFromUrlは、loadExistingInstantTargetによって使用されるTargetCollectionResourceを作成するために使用されるURLを使用して、プラットフォームコードから呼び出されます。 この例では、インスタントターゲットの読み込みが成功したときに、以前に保存された拡張が再作成されます。

loadExistingInstantTargetFromUrl: function (url) {
    var mapResource = new AR.TargetCollectionResource(url);
    this.tracker.loadExistingInstantTarget(mapResource, function () {
        var augmentations = JSON.parse(augmentationsJSON);

        World.instantTrackable.drawables.removeCamDrawable(World.drawables);
        World.drawables.forEach(function (drawable) {
            drawable.destroy();
        });
        World.drawables = [];
        augmentations.forEach(function (model) {
            var modelIndex = rotationValues.length;

            rotationValues[modelIndex] = model.rotate.z;
            scaleValues[modelIndex] = model.scale.x;

            World.drawables.push(new AR.Model(model.uri, {
                translate: model.translate,
                rotate: model.rotate,
                scale: model.scale,
                onDragBegan: function() {
                    oneFingerGestureAllowed = true;
                },
                onDragChanged: function(relativeX, relativeY, intersectionX, intersectionY) {
                    if (oneFingerGestureAllowed) {
                        // We recommend setting the entire translate property rather than
                        // its individual components as the latter would cause several
                        // call to native, which can potentially lead to performance
                        // issues on older devices. The same applied to the rotate and
                        // scale property
                        this.translate = {x:intersectionX, y:intersectionY};
                    }
                },
                onRotationChanged: function(angleInDegrees) {
                    this.rotate.z = rotationValues[modelIndex] - angleInDegrees;
                },
                onRotationEnded: function() {
                    rotationValues[modelIndex] = this.rotate.z
                },
                onScaleChanged: function(scale) {
                    var scaleValue = scaleValues[modelIndex] * scale;
                    this.scale = {x: scaleValue, y: scaleValue, z: scaleValue};
                },
                onScaleEnded: function() {
                    scaleValues[modelIndex] = this.scale.x;
                }
            }))
        });
        World.instantTrackable.drawables.addCamDrawable(World.drawables);
    }, function (error) {
        alert("Loading failed: " + error);
    }, {
        expansionPolicy: AR.CONST.INSTANT_TARGET_EXPANSION_POLICY.ALLOW_EXPANSION
    })
}

インスタントターゲットのパス設定

最初に、インスタントターゲットのパスが設定されます。

private final File instantTargetSaveLocation;

public SaveLoadInstantTargetExtension(final Activity activity, final ArchitectView architectView) {
    super(activity, architectView);
    instantTargetSaveFile = new File(activity.getExternalFilesDir(null), "SavedInstantTarget.wto");
    savedAugmentationsFile = new File(activity.getExternalFilesDir(null), "SavedAugmentations.json");
}

JavaScriptからJSONObjectを取得するには、JavaScriptInterfaceListenerが設定されます。

@Override
public void onCreate() {
    architectView.addArchitectJavaScriptInterfaceListener(this);
}

@Override
public void onDestroy() {
    architectView.removeArchitectJavaScriptInterfaceListener(this);
}

JSONオブジェクトが送信されると、それが解析され、インスタントターゲットを格納および読み込むパスがarchitectView.callJavascriptを使用してJavaScriptに送信されます。

@Override
public void onJSONObjectReceived(final JSONObject jsonObject) {
    try {
        switch (jsonObject.getString("action")) {
            case "save_current_instant_target":
                saveAugmentations(jsonObject.getString("augmentations"));
                saveCurrentInstantTarget();
                break;
            case "load_existing_instant_target":
                loadExistingInstantTarget();
                break;
        }
    } catch (JSONException e) {
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(activity, R.string.error_parsing_json, Toast.LENGTH_LONG).show();
            }
        });
    }
}

private void saveAugmentations(String data) {
    try (FileOutputStream stream = new FileOutputStream(savedAugmentationsFile)) {
        stream.write(data.getBytes());
    } catch (IOException e) {
        Log.e(TAG, "Could not save augmentations.", e);
    }
}

private String loadAugmentations() {
    int length = (int) savedAugmentationsFile.length();

    byte[] bytes = new byte[length];
    String jsonString;

    try (FileInputStream in = new FileInputStream(savedAugmentationsFile)) {
        in.read(bytes);
        jsonString = new String(bytes);
    } catch (IOException e) {
        jsonString = "";
    }

    return JSONObject.quote(jsonString);
}

private void loadExistingInstantTarget() {
    architectView.callJavascript(String.format("World.loadExistingInstantTargetFromUrl(\"%s\", %s)", instantTargetSaveFile.getAbsolutePath(), loadAugmentations()));
}

private void saveCurrentInstantTarget() {
    architectView.callJavascript(String.format("World.saveCurrentInstantTargetToUrl(\"%s\")", instantTargetSaveLocation.getAbsolutePath()));
}