一覧に戻る

iOS ネイティブAPIのインスタントトラッキング

2018.07.08iOSネイティブAPI

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

以下のセクションでは、Wikitude SDKネイティブAPIのインスタントトラッキング機能について詳しく説明します。最小の実装を紹介し、Wikitude SDKネイティブAPIが提供するシンプルさを紹介します。

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

SMARTは、ARKit、ARCore、WikitudeのSLAMエンジンをあらゆるデバイスに対応する単一の拡張現実SDK(クロスプラットフォーム)に統合したシームレスなAPIです。 SMARTはiOSデバイスの92.6%、市場で入手可能なAndroidデバイスの約35%をカバーし、多くのデバイスに最適なAR体験を提供します。
SMARTはデフォルトで有効になっていますが、WTInstantTrackerを作成するときにSMARTEnabledオプションを指定してパラメータを設定することで無効にすることができます。このオプションは実行時に動作を変更することはできません。

self.instantTracker = [self.wikitudeSDK.trackerManager createInstantTracker:self configuration:^(WTInstantTrackerConfiguration *instantTrackerConfiguration) {
    instantTrackerConfiguration.SMARTEnabled = NO;
}];

デバイスがSMARTをサポートしているかどうかを確認するには、isPlatformAssistedTrackingSupportedを使用します。

if ([self.wikitudeSDK.trackerManager isPlatformAssistedTrackingSupported])
{
    // device offers platform tracking capabilities (ARKit)
}

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

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

概要

インスタントトラッキングは、以前にWikitude SDKで導入されたものとは異なり、あらかじめ定義されたターゲットを認識し、トラッキングを開始するのではなく、ただちに任意の環境でトラッキングを開始するアルゴリズムです。これにより、非常に具体的なユースケースを実装することができます。

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

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

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

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

インスタントトラッキングの例は、インスタントトラッキングアルゴリズムの最小限の実装を提供します。まず、UIViewControllerにいくつかの追加が必要になります。インスタントトラッキングを使用するには、UIViewControllerがWTInstantTrackerDelegateプロトコルに準拠している必要があり、WTInstantTrackerとWTInstantTrackingStateがメンバーとして必要になります。

@interface WTInstantTrackerViewController () <WTWikitudeNativeSDKDelegate, WTInstantTrackerDelegate>
@property (nonatomic, strong) WTInstantTracker                      *instantTracker;

@property (nonatomic, assign) WTInstantTrackingState                trackingState;

WTWikitudeNativeSDKがviewDidAppearで実行されているかどうかを確認した後、WTInstantTrackerとWTInstantTrackingStateの両方を初期化します。

self.instantTracker = [self.wikitudeSDK.trackerManager createInstantTracker:self configuration:nil];
self.trackingState = WTInstantTrackerInitializing;

WTInstantTrackerは、以前に生成されたトラッカーインスタンスだけで最小限にインスタンス化することはできますが、実用的なユースケースでは、初期化状態とトラッキング状態の両方で描画されるドロアブルを提供することが推奨されます。したがって、StrokedRectangleインスタンスが生成されます。

self.renderableRectangle = [[StrokedRectangle alloc] init];
self.renderableRectangle.scale = 0.45;

StrokedRectangleをWTInstantTrackerの初期化に使用するには、WTInstantTrackerDelegateのinstantTracker:didChangeInitializationPose:コールバック関数を実装します。

- (void)instantTracker:(nonnull WTInstantTracker *)instantTracker didChangeInitializationPose:(nonnull WTInitializationPose *)pose
{
    [self.renderableRectangle setProjectionMatrix:pose.projection];
    [self.renderableRectangle setModelViewMatrix:pose.modelView];
}

このコールバック関数は、WTInstantTrackerと現在のWTInitializationPoseを提供します。このWitnitializationPoseは、StrokedRectangleの投影マトリックスとモデルビュー行列を設定するために使用され、初期化状態で正しく表示されます。

次に、ある状態から別の状態に移行する手段が必要です。このタスクでは、ボタンクリックで呼び出すことができるtoggleInstantTrackingState関数を提供します。self.trackingStateは、現在の状態の値を保持します。

- (IBAction)toggleInstantTrackingState:(id)sender
{
    if ( [[self.instantTrackingButton currentTitle] isEqualToString:@"Start Tracking"] )
    {
        if ( WTInstantTrackerInitializing == self.trackingState )
        {
            [self.instantTrackingButton setTitle:@"Start Initialization" forState:UIControlStateNormal];
            [self.instantTracker setActiveInstantTrackingState:WTInstantTrackerTracking];
        }
    }
    else
    {
        if ( WTInstantTrackerTracking == self.trackingState )
        {
            [self.instantTrackingButton setTitle:@"Start Tracking" forState:UIControlStateNormal];
            [self.instantTracker setActiveInstantTrackingState:WTInstantTrackerInitializing];
        }
    }
}

インスタントトラッカーが初期化されている間はオレンジ色の矩形を描画しますが、トラッキング状態になると矩形が青色になります。

- (void)instantTracker:(nonnull WTInstantTracker *)instantTracker didChangeState:(WTInstantTrackingState)newState
{
    _trackingState = newState;

    if (_trackingState == WTInstantTrackerInitializing) {
        [self.renderableRectangle setColor:[UIColor colorWithRed:1.00f green:0.58f blue:0.16f alpha:1.0f]];
    } else {
        [self.renderableRectangle setColor:[UIColor colorWithRed:0.41f green:0.60f blue:0.76f alpha:1.0f]];
    }
}

WTInstantTrackerのコールバック関数のinstantTracker:didChangeState:では、StrokedRectangleの色を制御して視覚的な表現をしています。しかしこれは矩形の色を変更するだけで、位置は変更しないため、矩形の投影行列とモデルビュー行列を更新するにはinstantTracker:didTrack:コールバック関数が必要です。

- (void)instantTracker:(nonnull WTInstantTracker *)instantTracker didTrack:(nonnull WTInstantTarget *)target
{
    [self.renderableRectangle setProjectionMatrix:target.projection];
    [self.renderableRectangle setModelViewMatrix:target.modelView];
}

次に、WTInstantTrackerのdeviceHeightプロパティを設定し、それをUISliderに接続するためにupdateDeviceHeightAboveGround:関数を用意しています。この変更は厳密に言えば必須ではありませんが、この方法で正確にデバイスの高さを指定することを推奨します。

- (IBAction)updateDeviceHeightAboveGround:(UISlider *)sender
{
    [self.instantTracker setDeviceHeightAboveGround:@(sender.value)];
}

最後に、状態が初期化からトラッキング状態に変更するときに高さのスライダを非表示にする必要があります。そのため、instantTracker:didChangeState:に適切なalpha値を設定してください。

dispatch_async(dispatch_get_main_queue(), ^{
    if ( WTInstantTrackerTracking == newState )
    {
        self.deviceHeightAboveGroundSlider.alpha = 0.0;
    }
    else
    {
        self.deviceHeightAboveGroundSlider.alpha = 1.0;
    }
});

このセクションで概説した例では、インジケータとして初期化状態のときはオレンジ色の矩形拡大をレンダリングし、トラッキング状態のときは青色の矩形拡大をレンダリングしています。この例はとても単純なものですが、インスタントトラッキングの基本概念を理解するのに適しています。

インスタントシーンのピッキング

インスタントトラッキング機能は、さらに、3Dポイントを基礎としたポイントクラウド構造から照会されることが可能になります。このセクションは、サンプルアプリケーションの対応するサンプルに基づいてこの機能を紹介します。
この機能を利用するには、画面上の2D入力位置が必要です。UITapGestureRecognizerは、その目的のためにView Controllerに追加されます。

@interface WTInstantScenePickingViewController () 
@property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer;
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapFrom:)];
[self.view addGestureRecognizer:_tapGestureRecognizer];
_tapGestureRecognizer.delegate = self;

handleTapFrom:recognizer関数はタップ位置を照会し、メインスクリーンスケールを使用してポイントからピクセルに変換し、結果の座標をconvertScreenCoordinate:toPointCloudCoordinateOnQueue:completion関数に渡すように実装されています。これらの入力座標は、画面の左上隅に原点を持ち、[0、screen_width_in_pixels]と[0、screen_height_in_pixels]の間隔内にあります。完了ハンドラ関数内では、ブール値と3Dポイントが受信します。前者は、操作が正常に完了したかどうかを通知します。後者は成功の場合に結果が含まれ、失敗の場合はゼロが含まれます。特定の間隔内の入力座標に対してポイントクラウドの位置が見つからないときはクエリが失敗することに注意してください。成功したクエリでは、StrokedCubeインスタンスが生成され、その結果の位置に拡張として表示されます。

CGPoint tapPosition = [recognizer locationInView:self.view];

CGFloat screenScale = [[UIScreen mainScreen] scale];
CGPoint tapPositionScaled = tapPosition;
tapPositionScaled.x *= screenScale;
tapPositionScaled.y *= screenScale;

[_instantTracker convertScreenCoordinate:tapPositionScaled toPointCloudCoordinateOnQueue:[NSOperationQueue currentQueue] completion:^(BOOL success, WTPoint3D* _Nullable pointCloudCoordinate) {
    if (pointCloudCoordinate) {
        StrokedCube *targetAugmentation = [[StrokedCube alloc] init];
        targetAugmentation.uniformScale = 0.05f;
        targetAugmentation.xTranslation = pointCloudCoordinate->x;
        targetAugmentation.yTranslation = pointCloudCoordinate->y;
        targetAugmentation.zTranslation = pointCloudCoordinate->z;
        [self.renderableCubes addObject:targetAugmentation];
    }
}];

最後に、サンプルを実行することで、トラッキング状態のときにキューブ拡張をスクリーンタッチに置くことができます。

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

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

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

インスタントターゲットを保存するには、アクティブなInsantTrackerがトラッキング状態でなければならず、指定されたパスのディレクトリが存在する必要があります。WTInstantTrackerメソッドの--saveCurrentInstantTargetWithSceneName:success:error:を使用して、実際に保存操作を実行します。以下のリスニングで詳細な説明を示します。ほとんどのコードは、ファイルを書き込めるディレクトリの作成/検証に関係しています。指定された名前を持つファイルが存在する場合、Wikitude SDKは既存のファイルを上書きします。豊富なユーザーのフィードバックを提供するために、Wikitude SDK APIは保存操作の失敗または成功の可能性を更新/通知するための成功と失敗のハンドラの両方を提供します。

- (IBAction)saveCurrentInstantTarget:(id)sender
{
    /* Create a directory where a file can be written to */
    NSArray<NSURL *> *paths = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    NSURL *documentDirectoryRoot = [paths objectAtIndex:0];

    NSURL *instantTargetsDirectory = [documentDirectoryRoot URLByAppendingPathComponent:@"Wikitude/InstantTargets" isDirectory:YES];

    BOOL isDirectory = NO;
    if ( ![[NSFileManager defaultManager] fileExistsAtPath:[instantTargetsDirectory absoluteString] isDirectory:&isDirectory] )
    {
        NSError *directoryCreationError = nil;
        BOOL directoryCreated = [[NSFileManager defaultManager] createDirectoryAtURL:instantTargetsDirectory withIntermediateDirectories:YES attributes:nil error:&directoryCreationError];
        if ( !directoryCreated )
        {
            NSLog(@"Unable to create Document/Wikitude/InstantTargets directory inside app sandbox. Unable to save current instant target.");
            return;
        }
    }

    NSURL *instantTargetURL = [instantTargetsDirectory URLByAppendingPathComponent:@"currentInstantTarget.wto"];

    [self.navigationItem setPrompt:@"Saving instant target..."];

    [self.instantTracker saveCurrentInstantTargetWithSceneName:instantTargetURL success:^(NSString *scenePath) {
        NSLog(@"Instant target saved at path '%@'", scenePath);
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.navigationItem setPrompt:nil];
        });
    } error:^(NSError *error) {
        NSLog(@"Error while saving instant target to file. '%@'", [error localizedDescription]);
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertController *errorSavingCurrentInstantTargetController = [UIAlertController alertControllerWithTitle:@"Error saving instant target" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
            [self presentViewController:errorSavingCurrentInstantTargetController animated:YES completion:nil];
        });
    }];
}

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

インスタントターゲットを読み込むには、アクティブトラッカーと以前に保存したインスタントターゲットがなければなりません。
InstantTargetRestorationConfigurationは、読み込まれたインスタントターゲットの動作を定義します。この例では、ポリシーはALLOW_EXPANSIONに設定されます。これは、Wikitude SDKがトラッキング可能な新しいポイントを見つけようとすることを意味します。

[self.instantTracker loadExistingInstantTargetWithTargetCollectionResource:instantTargetCollectionResource restoration:^(WTInstantTargetRestorationConfiguration *configuration) {
        [configuration setInstantTargetExpansionPolicy:WTInstantTargetExpansionPolicyAllowExpansion];
    } success:^(NSString *scenePath) {
        // ...  
    } error:^(NSError *error) {
        // ...
}];

以前に保存したインスタントターゲットを読み込むには、WTInstantTracker -saveCurrentInstantTargetWithSceneName:success:error:によって保存されたファイルからWTTargetCollectionResourceを作成する必要があります。NSURL API +fileURLWithPath:は、iOSアプリケーションがファイルを開くことができるURLを取得するために使用されることに注意してください。

WTTargetCollectionResource *instantTargetCollectionResource = [[self.wikitudeSDK trackerManager] createTargetCollectionResourceFromURL:[NSURL fileURLWithPath:instantTargetFilePath]];

次のスニペットは、.wtoファイルへの初期パスの生成方法を示しています。

/* Check if a instant target is stored at a predefined path */
NSString* documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];

NSString *instantTargetFilePath = [documentsPath stringByAppendingPathComponent:@"Wikitude/InstantTargets/currentInstantTarget.wto"];
BOOL instantTargetFileExists = [[NSFileManager defaultManager] fileExistsAtPath:instantTargetFilePath];

インスタントターゲットが読み込まれた後、トラッカーは即座にターゲットを探してトラッキングしようとします。特定のスレッドでコールバックを受け取るハンドラを提供することはオプションです。 次のリスニングは、潜在的な読み込みインスタントターゲットの完全な実装を示しています。ユーザーのフィードバックを提供するために使用できる成功とエラーハンドラに注意してください。

- (IBAction)loadInstantTarget:(id)sender
{
    /* Check if a instant target is stored at a predefined path */
    NSString* documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];

    NSString *instantTargetFilePath = [documentsPath stringByAppendingPathComponent:@"Wikitude/InstantTargets/currentInstantTarget.wto"];
    BOOL instantTargetFileExists = [[NSFileManager defaultManager] fileExistsAtPath:instantTargetFilePath];
    if ( !instantTargetFileExists )
    {
        NSLog(@"No instant target file found at the given path '%@'", instantTargetFilePath);
        return;
    }
    else
    {
        [self.navigationItem setPrompt:@"Loading instant target..."];
        WTTargetCollectionResource *instantTargetCollectionResource = [[self.wikitudeSDK trackerManager] createTargetCollectionResourceFromURL:[NSURL fileURLWithPath:instantTargetFilePath]];
        [self.instantTracker loadExistingInstantTargetWithTargetCollectionResource:instantTargetCollectionResource restoration:^(WTInstantTargetRestorationConfiguration *configuration) {
            [configuration setInstantTargetExpansionPolicy:WTInstantTargetExpansionPolicyAllowExpansion];
        } success:^(NSString *scenePath) {
            NSLog(@"successfully restored instant target from path '%@'", instantTargetFilePath);
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.navigationItem setPrompt:nil];
            });
        } error:^(NSError *error) {
            NSLog(@"Failed to load instant target from path '%@'", instantTargetFilePath);
            dispatch_async(dispatch_get_main_queue(), ^{
                UIAlertController *errorLoadingInstantTargetAlertController = [UIAlertController alertControllerWithTitle:@"Error loading instant target" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
                [self presentViewController:errorLoadingInstantTargetAlertController animated:YES completion:nil];
            });
        }];
    }
}