見出し画像

Magic Leap PCF(アンカー)の視覚化 - Unity

概要

Magic Leap 1は、環境のローカライズとマッピングを同時に行うことができます。 これを行うために、Magic Leap 1 は、永続座標フレーム(PCF)として知られる静的ジオメトリ上に座標を作成します。 次に、これらのPCFをグループ化してマップを作成します。このマップを使用して、Magic Leap 1が新しい場所で使用されているのか以前にマップされた場所で使用されているのかを判断します。

この記事は、Magic Leap Developer Portal の 「Visualize Magic Leap PCFs (Anchors)」の内容を元に作成した記事になります。

目的

このチュートリアルでは、以下のことを行います。

PCFsサービスの初期化を行います。
PCFsサービスを初期化する。
見つかった各座標を表すオブジェクトをインスタンス化する。

開発環境

Lumin SDK 0.26 + Unity Editor 2020.3

はじめる前に

プロジェクトがLuminに設定され、Lumin SDKパッケージがインポートされていることを確認します。

Privileges(特権)

PcfReadの設定が必要です。今回、Controlを使用するためController Poseの設定も行います。

API Level

Level 6 以上の設定が必要です。

1. 新規シーンの作成とCameraの置き換え

新規シーンを作成後、デフォルトの「Main Camera」を「Magic Leap Main Camera」に置き換えます。

ヒエラルキーにある、Main Cameraを削除します。

Package → Magic Leap SDK → Tools → Prefabs → Main Camera prefabをヒエラルキーにドラッグ&ドロップします。

画像1

Persistent Object Prefab の作成

Persistent Object を可視化するためのPrefabを作成します。

シーン内にCubeを新規作成します。名前は PCFVisual とします。
スケールを 0.20 , 0.20 , 0.20 に設定します。
これを Assets フォルダにドラッグしてPrefabを作成します。
ヒエラルキーにあるPCFVisualを削除します。

PCF(アンカー)の視覚化

このセクションでは、Magic Leapが空間にマッピングした座標を視覚化するためのスクリプトを作成します。新しいスクリプトSimplePCFVisualizerを作成し、スクリプトを開いて、Update()メソッドを削除します。

using UnityEngine;

public class SimplePcfVisualizer : MonoBehaviour
{
   // Start is called before the first frame update
   void Start()
   {
       
   }
}

PCFのサービスの開始

Magic Leap のPersistent Coordinateスクリプトにアクセスするには、スクリプトの先頭にUnityEngine.XR.MagicLeapを追加します。 Startメソッドの中で、#if PLATFORM_LUMIN を追加して、Lumin Platformをターゲットにしている場合にのみコードがコンパイルされるようにします。 そしてMLPersistentCoordinateFrames.Start()を呼び出して、Persistent Coordinate Framesの検索を開始するようにします。 このメソッドの結果を見れば、リクエストが成功したかどうかがわかります。成功しなかった場合は、Debug.LogError()でエラー情報をデバッグ出力後、処理を中止します。

using UnityEngine;
using UnityEngine.XR.MagicLeap;

public class SimplePcfVisualizer : MonoBehaviour
{
   // Start is called before the first frame update
   void Start()
   {
       //PCFs can only be started when building for Lumin
#if PLATFORM_LUMIN

       //Ask the Magic Leap to start looking for Persistent Coordinate Frames.
       //The result will let us know if the service could start.
       MLResult result = MLPersistentCoordinateFrames.Start();

       //If our request was not successful...
       if (!result.IsOk)
       {
           //Inform the user about the error in the debug log.
            Debug.LogError("Error: Failed starting MLPersistentCoordinateFrames, disabling script. Reason:" + result);

           //Since we need the service to start successfully, we disable the script if it doesn't.
           enabled = false;

           //Return to prevent further initialization
           return;
       }
#endif
   }
}

PCFsを可視化する

次にMLPersistentCoordinateFrames.FindAllPCFs()を使ってシーン内のPCFを検索します。このメソッドは、現在のマップにあるPCFのリストを出力します。検索したいPCFの種類など、追加の引数を指定できます。

Note FindAllPCFsの呼び出しは処理コストが高いため、繰り返し呼び出すべきではありません。

PCFの検索

マップ上のPCFを検索するメソッドを作ってみましょう。DisplayAllPCFs()という新しいメソッドを作成し、MLPersistentCoordinateFrames.FindAllPCFs()を呼び出して、既存の座標を照会します。このメソッドは、リクエストが成功したかどうかを判断は、返却されたMLResultで判断します。

  void DisplayAllPCFs()
   {
  //PCFs can only be searched when building for Lumin
#if PLATFORM_LUMIN

       // Outputs the PCFs found in the current map.
       MLResult result = MLPersistentCoordinateFrames.FindAllPCFs(out List<MLPersistentCoordinateFrames.PCF> allPCFs);
       
       //If the request was not successful..
       if (!result.IsOk)
       {
           if (result.Result == MLResult.Code.PassableWorldLowMapQuality || result.Result == MLResult.Code.PassableWorldUnableToLocalize)
           {
               Debug.LogWarningFormat("Map quality not sufficient enough for PCFVisualizer to find all pcfs. Reason: {0}", result);
           }
           else
           {
               Debug.LogErrorFormat("Error: PCFVisualizer failed to find all PCFs because MLPersistentCoordinateFrames failed to get all PCFs. Reason: {0}", result);
           }
       }
#endif
   }

PCFの表示

スクリプトの先頭に、見つかったPCF表示用のGameObjectを追加します。 さらに、スクリプトの先頭にUnityEngine.XR.MagicLeapというusingディレクティブを追加します。PCFのIDを使って作成済みのPCFを持つためのDictionaryを作成します。これにより、新しいPCFと既存のPCFを区別することができ、無効になったときに破棄することができます。

using UnityEngine;
using UnityEngine.XR.MagicLeap;
using System.Collections.Generic;

public class SimplePcfVisualizer : MonoBehaviour
{
   [SerializeField]//The visual we will create at each PCF
   private GameObject pcfVisualPrefab;

   //Track the visuals we already created to avoid duplicates
   private Dictionary<string, GameObject> _visualObjectsByString = new Dictionary<string, GameObject>();

//...

PCFの状態をチェックする方法は2つあります。1つは、StartメソッドでMLPersistentCoordinateFrames.PCF.OnStatusChangeイベントを使用する方法です。OnStatusChangeイベントは、任意のPCFが更新されたり、作成されたり、破棄されたりしたときに呼び出されます。

なお、ステータスの更新を行うためには、MLPersistentCoordinateFrames.FindAllPCFs()を呼び出す必要があります。

もう1つの方法は、MLPersistentCoordinateFrames.FindAllPCFs()を使用する方法です。この方法の場合、ステータスが変化していなくても、PCFの完全なリストを繰り返し検索を実施するため、時間がかかります。 このチュートリアルでは、デモンストレーションのために、後者の方法を使用します。

次のコードをDisplayAllPCFs()メソッドに追加します。

//Iterate through all of the PCFs that were output from the FindAllPCFs call.
foreach (var pcf in allPCFs)
{
    //If our dictionary contains a value for the PCFs ID...
    if (_visualObjectsByString.TryGetValue(pcf.CFUID.ToString(), out GameObject pcfVisual))
    {
        //Update it's position and rotation.
        pcfVisual.transform.position = pcf.Position;
        pcfVisual.transform.rotation = pcf.Rotation;

    } // If we have not created the PCF and the PCF is new...
    else if (pcf.CurrentStatus == MLPersistentCoordinateFrames.PCF.Status.Created)
    {
        //Instantiate the visual using the PCFs position and rotation.
        GameObject newPcfVisual = Instantiate(pcfVisualPrefab,
            pcf.Position, pcf.Rotation, transform);

        //Track the visual in our dictionary.
        _visualObjectsByString.Add(pcf.CFUID.ToString(), newPcfVisual);
    }
}

最後に、作成したメソッドを呼び出します。Start()メソッドの一番下に呼び出しています。

Note StartでFindAllPCFs()を呼び出すと、ヘッドセットのローカライズが完了していない可能性があるため、PCFが見つからないことがあります。

    // Start is called before the first frame update
   void Start()
   {
       //PCFs can only be started when building for Lumin
#if PLATFORM_LUMIN

       //Ask the Magic Leap to start looking for Persistent Coordinate Frames.
       //The result will let us know if the service could start.
       MLResult result = MLPersistentCoordinateFrames.Start();

       //If our request was not successful...
       if (!result.IsOk)
       {
           //Inform the user about the error in the debug log.
           Debug.LogError("Error: Failed starting MLPersistentCoordinateFrames, disabling script. Reason:" + result);

           //Since we need the service to start successfully, we disable the script if it doesn't.
           enabled = false;

           //Return to prevent further initialization
           return;
       }

       //Create the PCF Visuals.
       //TODO (may not work) still needs to handle localization latency.
       DisplayAllPCFs();

#endif
   }

トラッキングロスとローカリゼーションレイテンシーへの対応

プレイヤーがアプリの使用中にトラッキングを失う可能性があることを考慮し、広いマップにローカライズするのにかかる時間も考慮しなければなりません。ここでは、MLPersistentCoordinateFrames.OnLocalizedアクションをサブスクライブして、ローカリゼーションの状態をトラッキングすることで実現できます。

Start()メソッドで、MLPersistentCoordinateFramesがローカライズされているかどうかをチェックします。ローカライズされていれば DisplayAllPCFs()メソッドを呼び出します。次に HandleOnLocalized(bool localized) というメソッドを作成し、Start() の MLPersistentCoordinateFrames.OnLocalized にサブスクライブします。

 // Start is called before the first frame update
   void Start()
   {
...
       //Handle Localization status changes
       MLPersistentCoordinateFrames.OnLocalized += HandleOnLocalized;

       //Create the PCF Visuals only if we are already localized
       if (MLPersistentCoordinateFrames.IsLocalized)
       {
           DisplayAllPCFs();
       }

   }

スクリプトのテスト

スクリプトが完成したら、空のGameObjectに追加して、pcfVisualPrefabパラメータにオブジェクトを割り当てます。

Zero Iterationを使ってEditorでテストすることもできますし、セットアップガイドを完了し、有効な識別情報の詳細を持ち、開発者証明書を持っている場合は、アプリケーションをデバイスにデプロイすることができます。

SimplePcfVisualizer.cs

using UnityEngine;
using UnityEngine.XR.MagicLeap;
using System.Collections.Generic;

public class SimplePcfVisualizer : MonoBehaviour
{
   [SerializeField]//The visual we will create at each PCF
   private GameObject pcfVisualPrefab;

   //Track the visuals we already created to avoid duplicates
   private Dictionary<string, GameObject> _visualObjectsByString = new Dictionary<string, GameObject>();

   // Start is called before the first frame update
   void Start()
   {
       //PCFs can only be started when building for Lumin
#if PLATFORM_LUMIN

       //Ask the Magic Leap to start looking for Persistent Coordinate Frames.
       //The result will let us know if the service could start.
       MLResult result = MLPersistentCoordinateFrames.Start();

       //If our request was not successful...
       if (!result.IsOk)
       {
           //Inform the user about the error in the debug log.
           Debug.LogError("Error: Failed starting MLPersistentCoordinateFrames, disabling script. Reason:" + result);

           //Since we need the service to start successfully, we disable the script if it doesn't.
           enabled = false;

           //Return to prevent further initialization
           return;
       }

       //Handle Localization status changes
       MLPersistentCoordinateFrames.OnLocalized += HandleOnLocalized;

       //Create the PCF Visuals only if we are already localized
       if (MLPersistentCoordinateFrames.IsLocalized)
       {
           DisplayAllPCFs();
       }
#endif
   }

   /// Handles the logic when localization is gained or lost.
   private void HandleOnLocalized(bool localized)
   {
       if (localized)
       {
           //Display/Update PCFs if localized.
           DisplayAllPCFs();
       }
   }

   void DisplayAllPCFs()
   {
#if PLATFORM_LUMIN

       // Outputs the PCFs found in the current map.
       MLResult result = MLPersistentCoordinateFrames.FindAllPCFs(out List<MLPersistentCoordinateFrames.PCF> allPCFs);

       //If the request was not successful..
       if (!result.IsOk)
       {
           if (result.Result == MLResult.Code.PassableWorldLowMapQuality || result.Result == MLResult.Code.PassableWorldUnableToLocalize)
           {
               Debug.LogWarningFormat("Map quality not sufficient enough for PCFVisualizer to find all pcfs. Reason: {0}", result);
           }
           else
           {
               Debug.LogErrorFormat("Error: PCFVisualizer failed to find all PCFs because MLPersistentCoordinateFrames failed to get all PCFs. Reason: {0}", result);
           }
       }

       //Iterate through all of the PCFs that were output from the FindAllPCFs call.
       foreach (var pcf in allPCFs)
       {
           //If our dictionary contains a value for the PCFs ID...
           if (_visualObjectsByString.TryGetValue(pcf.CFUID.ToString(), out GameObject pcfVisual))
           {
               //Update it's position and rotation.
               pcfVisual.transform.position = pcf.Position;
               pcfVisual.transform.rotation = pcf.Rotation;

           } // If we have not created the PCF and the PCF is new...
           else if (pcf.CurrentStatus == MLPersistentCoordinateFrames.PCF.Status.Created)
           {
               //Instantiate the visual using the PCFs position and rotation.
               GameObject newPcfVisual = Instantiate(pcfVisualPrefab,
                   pcf.Position, pcf.Rotation, transform);

               //Track the visual in our dictionary.
               _visualObjectsByString.Add(pcf.CFUID.ToString(), newPcfVisual);
           }
#endif
       }
   }
}

最後に

OnePlanet XR「OnePlanet XR」はAR/MR技術に専門特化したコンサルティングサービスです。豊富な実績を元に、AR/MR技術を活用した新たな事業の立ち上げ支援や、社内業務のデジタル化/DX推進など、貴社の必要とするイノベーションを実現いたします。

MRグラスを活用した3Dモデル設置シミュレーション

ご相談から受け付けております。ご興味ございましたら弊社までお問い合わせください。

お問い合わせ先:https://1planet.co.jp/xrconsulting.html

OnePlanet Tech Magazine