本日は HoloLens2 の小ネタ枠です。
SampleQRCodesプロジェクト内の処理を確認してQRコード追従の仕組みを理解します。
今回はスポーンオブジェクトでのQRコードの情報取得の処理を確認します。
SampleQRCodesプロジェクト
SampleQRCodes プロジェクトの試し方については以下を参照ください。
bluebirdofoz.hatenablog.com
QRコード検出スクリプト
SampleQRCodes プロジェクトでシーンにアタッチされているQRコードの検出スクリプトは以下の2つです。
・QRCodeManager
・QRCodesVisualizer
また、QRCodesVisualizer で参照しているスポーン用オブジェクトには以下の2つのスクリプトがアタッチされています。
・QRCode
・SpatialGraphCoordinateSystem
スクリプトの確認
今回はスポーン用オブジェクトの2つのスクリプトを確認して、QRコードの情報取得を理解します。
スクリプトの役割と各コードの処理や参照ドキュメントをコメントとして追記しました。
QRCode.cs
本スクリプトは割り当てられたQRコードの情報を参照し、スポーンオブジェクトの検出範囲UIと情報テキストを更新します。
using System.Collections; using System.Collections.Generic; using UnityEngine; #if WINDOWS_UWP using Windows.Perception.Spatial; #endif namespace QRTracking { [RequireComponent(typeof(QRTracking.SpatialGraphCoordinateSystem))] public class QRCode : MonoBehaviour { // 検出したQRコードのデータ参照 // 本スクリプトの起動に必須 public Microsoft.MixedReality.QR.QRCode qrCode; // QRコードの検出範囲を示す"Cube"パーツの参照 private GameObject qrCodeCube; // QRコードの物理サイズ情報 public float PhysicalSize { get; private set; } // QRコードの保持テキスト情報 public string CodeText { get; private set; } // ID,ノードID,テキスト,バージョン,タイムスタンプ,サイズの // 表示用テキストメッシュへの参照 private TextMesh QRID; private TextMesh QRNodeID; private TextMesh QRText; private TextMesh QRVersion; private TextMesh QRTimeStamp; private TextMesh QRSize; private GameObject QRInfo; // QRコードのテキストが URI か否か private bool validURI = false; // URI の起動判定フラグ private bool launch = false; // QRコードのテキストから生成されたURIインスタンス private System.Uri uriResult; // 最新のQRコードの検出時タイムスタンプ private long lastTimeStamp = 0; /// <summary> /// 初期化処理 /// </summary> void Start() { // 未検出状態の変数の初期化 PhysicalSize = 0.1f; CodeText = "Dummy"; // QRコードのデータ参照が設定されているか否か if (qrCode == null) { // QRコードのデータ参照が設定されていない場合は // エラーと判定する throw new System.Exception("QR Code Empty"); } // QRコードのデータ参照から物理サイズと保持テキストを取得する PhysicalSize = qrCode.PhysicalSideLength; CodeText = qrCode.Data; // QRコードの検出範囲を示すオブジェクトの参照を取得する qrCodeCube = gameObject.transform.Find("Cube").gameObject; // ID,ノードID,テキスト,バージョン,タイムスタンプ,サイズの // QRコードの情報を表示するテキストメッシュの参照を取得する QRInfo = gameObject.transform.Find("QRInfo").gameObject; QRID = QRInfo.transform.Find("QRID").gameObject.GetComponent<TextMesh>(); QRNodeID = QRInfo.transform.Find("QRNodeID").gameObject.GetComponent<TextMesh>(); QRText = QRInfo.transform.Find("QRText").gameObject.GetComponent<TextMesh>(); QRVersion = QRInfo.transform.Find("QRVersion").gameObject.GetComponent<TextMesh>(); QRTimeStamp = QRInfo.transform.Find("QRTimeStamp").gameObject.GetComponent<TextMesh>(); QRSize = QRInfo.transform.Find("QRSize").gameObject.GetComponent<TextMesh>(); // ID,ノードID,テキストの情報をテキストメッシュに表示する QRID.text = "Id:" + qrCode.Id.ToString(); QRNodeID.text = "NodeId:" + qrCode.SpatialGraphNodeId.ToString(); QRText.text = CodeText; // QRコードのテキスト情報から URI の生成をトライする // https://docs.microsoft.com/ja-jp/dotnet/api/system.uri.trycreate if (System.Uri.TryCreate(CodeText, System.UriKind.Absolute,out uriResult)) { // URI が生成できた場合 // URI フラグをONにしてテキストメッシュを青色に変更する validURI = true; QRText.color = Color.blue; } // バージョン,タイムスタンプ,サイズの情報をテキストメッシュに表示する QRVersion.text = "Ver: " + qrCode.Version; QRSize.text = "Size:" + qrCode.PhysicalSideLength.ToString("F04") + "m"; QRTimeStamp.text = "Time:" + qrCode.LastDetectedTime.ToString("MM/dd/yyyy HH:mm:ss.fff"); QRTimeStamp.color = Color.yellow; // QRコードの情報をログ出力する Debug.Log("Id= " + qrCode.Id + "NodeId= " + qrCode.SpatialGraphNodeId + " PhysicalSize = " + PhysicalSize + " TimeStamp = " + qrCode.SystemRelativeLastDetectedTime.Ticks + " QRVersion = " + qrCode.Version + " QRData = " + CodeText); } /// <summary> /// プロパティ表示を更新する /// </summary> void UpdatePropertiesDisplay() { // QRコードの情報が更新されていた場合のみ、表示の更新を行う if (qrCode != null && lastTimeStamp != qrCode.SystemRelativeLastDetectedTime.Ticks) { // QRコードのサイズ情報をテキストメッシュに表示する QRSize.text = "Size:" + qrCode.PhysicalSideLength.ToString("F04") + "m"; // QRコードのタイムスタンプ情報をテキストメッシュに表示する // 変更のたびに文字色を黄色と白色で切り替える QRTimeStamp.text = "Time:" + qrCode.LastDetectedTime.ToString("MM/dd/yyyy HH:mm:ss.fff"); QRTimeStamp.color = QRTimeStamp.color==Color.yellow? Color.white: Color.yellow; // QRコードのサイズ情報を取得する PhysicalSize = qrCode.PhysicalSideLength; Debug.Log("Id= " + qrCode.Id + "NodeId= " + qrCode.SpatialGraphNodeId + " PhysicalSize = " + PhysicalSize + " TimeStamp = " + qrCode.SystemRelativeLastDetectedTime.Ticks + " Time = " + qrCode.LastDetectedTime.ToString("MM/dd/yyyy HH:mm:ss.fff")); // サイズ情報を元に検出範囲オブジェクトの中央位置とサイズを調整する qrCodeCube.transform.localPosition = new Vector3(PhysicalSize / 2.0f, PhysicalSize / 2.0f, 0.0f); qrCodeCube.transform.localScale = new Vector3(PhysicalSize, PhysicalSize, 0.005f); // タイムスタンプを更新する lastTimeStamp = qrCode.SystemRelativeLastDetectedTime.Ticks; // サイズ情報を元にテキストメッシュの表示位置を調整する QRInfo.transform.localScale = new Vector3(PhysicalSize/0.2f, PhysicalSize / 0.2f, PhysicalSize / 0.2f); } } /// <summary> /// 定期処理 /// </summary> void Update() { // プロパティ表示を更新する UpdatePropertiesDisplay(); if (launch) { // URI の起動判定フラグが ON の場合 // URI をランチャーで開く launch = false; LaunchUri(); } } /// <summary> /// URI をランチャーで開く /// </summary> void LaunchUri() { // HoloLens2 上でのみ WSA の API を利用して URI をランチャーで開く // URI に関連付けられたデフォルトアプリケーションが起動する // https://docs.unity3d.com/jp/current/ScriptReference/WSA.Launcher.LaunchUri.html #if WINDOWS_UWP // Launch the URI UnityEngine.WSA.Launcher.LaunchUri(uriResult.ToString(), true); #endif } /// <summary> /// クリック時処理 /// (SampleQRCodesでは未使用) /// </summary> public void OnInputClicked() { if (validURI) { // QRコードのテキストが URI であれば // URI の起動判定フラグを ON に設定する launch = true; } // eventData.Use(); // Mark the event as used, so it doesn't fall through to other handlers. } } }
SpatialGraphCoordinateSystem.cs
ノードIDから HoloLens が保有する空間座標系の情報を取得します。
座標の情報を Unity シーンの座標系に変換し、アタッチしているオブジェクトの座標と回転に反映させます。
using System.Collections; using System.Collections.Generic; using UnityEngine; #if WINDOWS_UWP using Windows.Perception.Spatial; #endif using Microsoft.MixedReality.Toolkit.Utilities; namespace QRTracking { public class SpatialGraphCoordinateSystem : MonoBehaviour { #if WINDOWS_UWP // 現実空間に置けるユーザの周囲情報で使用する空間座標系の参照 // https://docs.microsoft.com/en-us/uwp/api/windows.perception.spatial.spatialcoordinatesystem private SpatialCoordinateSystem CoordinateSystem = null; #endif // 座標情報を取得する静的空間のノードID // SampleQRCodesではQRコードのノードIDが設定される private System.Guid id; public System.Guid Id { get { return id; } set { id = value; #if WINDOWS_UWP // 静的空間のノードIDを指定して参照する空間座標系を取得する // https://docs.microsoft.com/en-us/uwp/api/windows.perception.spatial.preview.spatialgraphinteroppreview.createcoordinatesystemfornode CoordinateSystem = Windows.Perception.Spatial.Preview.SpatialGraphInteropPreview.CreateCoordinateSystemForNode(id); if (CoordinateSystem == null) { // 取得できなかった場合はログを出力する Debug.Log("Id= " + id + " Failed to acquire coordinate system"); } #endif } } void Awake() { } /// <summary> /// 起動処理 /// </summary> void Start() { #if WINDOWS_UWP // 空間座標系の参照を未取得か否か if (CoordinateSystem == null) { // 静的空間のノードIDを指定して参照する空間座標系を取得する // https://docs.microsoft.com/en-us/uwp/api/windows.perception.spatial.preview.spatialgraphinteroppreview.createcoordinatesystemfornode CoordinateSystem = Windows.Perception.Spatial.Preview.SpatialGraphInteropPreview.CreateCoordinateSystemForNode(id); if (CoordinateSystem == null) { // 取得できなかった場合はログを出力する Debug.Log("Id= " + id + " Failed to acquire coordinate system"); } } #endif } /// <summary> /// オブジェクトの座標を参照の空間座標系に合わせて更新する /// </summary> private void UpdateLocation() { { #if WINDOWS_UWP // 空間座標系の参照を未取得か否か if (CoordinateSystem == null) { // 静的空間のノードIDを指定して参照する空間座標系を取得する // https://docs.microsoft.com/en-us/uwp/api/windows.perception.spatial.preview.spatialgraphinteroppreview.createcoordinatesystemfornode CoordinateSystem = Windows.Perception.Spatial.Preview.SpatialGraphInteropPreview.CreateCoordinateSystemForNode(id); if (CoordinateSystem == null) { // 取得できなかった場合はログを出力する Debug.Log("Id= " + id + " Failed to acquire coordinate system"); } } // 空間座標系の参照を未取得か否か if (CoordinateSystem != null) { // 座標(Position)と回転(Rotation)の変数を初期化 Quaternion rotation = Quaternion.identity; Vector3 translation = new Vector3(0.0f, 0.0f, 0.0f); // 現在の実行環境のワールドオリジンを指定する SpatialCoordinateSystem 参照(ポインタ)を取得する // https://docs.unity3d.com/Packages/com.unity.xr.windowsmr@2.4/api/UnityEngine.XR.WindowsMR.WindowsMREnvironment.html System.IntPtr rootCoordnateSystemPtr = UnityEngine.XR.WindowsMR.WindowsMREnvironment.OriginSpatialCoordinateSystem; // 取得したポインタを基に SpatialCoordinateSystem の参照を取得する // https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.marshal.getobjectforiunknown SpatialCoordinateSystem rootSpatialCoordinateSystem = (SpatialCoordinateSystem)System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(rootCoordnateSystemPtr); // Get the relative transform from the unity origin // 空間座標系から Unity シーンの原点からの相対座標の行列を生成します // https://docs.microsoft.com/ja-jp/uwp/api/windows.perception.spatial.spatialcoordinatesystem.trygettransformto System.Numerics.Matrix4x4? relativePose = CoordinateSystem.TryGetTransformTo(rootSpatialCoordinateSystem); // 座標行列を生成できたか否か if (relativePose != null) { // 座標、回転、スケール System.Numerics.Vector3 scale; System.Numerics.Quaternion rotation1; System.Numerics.Vector3 translation1; // 座標行列 System.Numerics.Matrix4x4 newMatrix = relativePose.Value; // Platform coordinates are all right handed and unity uses left handed matrices. so we convert the matrix // from rhs-rhs to lhs-lhs // Convert from right to left coordinate system // UWP プラットフォームの座標はすべて右手座標系であり、Unity は左手座標系を使用します。 // このため、行列を rhs - rhs から lhs - lhs に変換する必要があります。 // (右手座標系から左手座標系の座標系に変換します) newMatrix.M13 = -newMatrix.M13; newMatrix.M23 = -newMatrix.M23; newMatrix.M43 = -newMatrix.M43; newMatrix.M31 = -newMatrix.M31; newMatrix.M32 = -newMatrix.M32; newMatrix.M34 = -newMatrix.M34; // 指定された平行移動行列から拡大縮小/座標/回転の成分の抽出を試みます // 戻り値は演算が成功したかどうかを示します // https://docs.microsoft.com/ja-jp/dotnet/api/system.numerics.matrix4x4.decompose System.Numerics.Matrix4x4.Decompose(newMatrix, out scale, out rotation1, out translation1); // 演算結果の座標と回転情報から Unity クラスの Pose 情報を作成する // https://docs.unity3d.com/ja/2018.4/ScriptReference/Pose.html translation = new Vector3(translation1.X, translation1.Y, translation1.Z); rotation = new Quaternion(rotation1.X, rotation1.Y, rotation1.Z, rotation1.W); Pose pose = new Pose(translation, rotation); // If there is a parent to the camera that means we are using teleport and we should not apply the teleport // to these objects so apply the inverse // カメラの親が存在する場合 // (つまりテレポートを使用していてオブジェクトにテレポートの座標系を適用するべきではない場合) // カメラの親のローカル空間の座標に変換してから適用します // https://docs.unity3d.com/ja/current/ScriptReference/Pose.GetTransformedBy.html if (CameraCache.Main.transform.parent != null) { pose = pose.GetTransformedBy(CameraCache.Main.transform.parent); } // 取得した座標と回転情報をオブジェクトに適用します gameObject.transform.SetPositionAndRotation(pose.position, pose.rotation); //Debug.Log("Id= " + id + " QRPose = " + pose.position.ToString("F7") + " QRRot = " + pose.rotation.ToString("F7")); } else { // 変換行列の生成に失敗した場合 // オブジェクトの移動は行わない // Debug.Log("Id= " + id + " Unable to locate qrcode" ); } } else { // 空間座標系の参照を未取得の場合 // オブジェクトを無効化する gameObject.SetActive(false); } #endif } } /// <summary> /// 定期処理 /// </summary> void Update() { // オブジェクトの座標を参照の空間座標系に合わせて更新する UpdateLocation(); } } }