本日は HoloToolKit の調査枠です。
SharingWithUNET のサンプルシーンを確認していきます。
bluebirdofoz.hatenablog.com
今回は UNETAnchorManager オブジェクトにある UNetAnchorManager について機能を確認します。
UNetAnchorManager はワールドアンカーの作成・受取、これを用いた共通座標の設定を行います。
サーバの場合、本クラスの CreateAnchor() が呼び出され、アンカーを設定するのに適した位置を見つけます。
アンカーを作成したら、これをシリアル化し、GenericNetworkTransmitter クラスを通してクライアントに送信します。
クライアントの場合、GenericNetworkTransmitter クラスの受信イベントでアンカーデータをキャッチします。
アンカーデータをデシリアライズし、これをWorldAnchorManagerに記録します。
Anchor Name を同期することで、記録したアンカーの内、全てのホストは同名のアンカーを利用することになります。
アンカーは SharedCollection のインスタンスを持つゲームオブジェクトに設定され、これが共通の座標となります。
自作アプリを組み込む際にはひとまずノータッチでも問題なさそうです。
ただ、本スクリプトと GenericNetworkTransmitter スクリプトはシェアリングのワールドアンカーの取り扱いをまとめたスクリプトですので、理解を深めて損はなさそうです。
Anchor Name
現在利用中のアンカーネームが設定される。本変数には SyncVar 属性が設定されている。
SyncVar は NetworkBehaviour クラスのメンバ変数に置くことができる属性。
これらの変数は、サーバーから準備状態にあるゲーム内のクライアントに値が同期化される。
アンカーの設定位置を決定するのは以下の FindAnchorPosition 関数です。
SharingWithUNET では spatialMapping から最も頂点密度が高いサーフェスを検出して、そこをアンカーに定めます。
/// <summary> /// Finds a good position to set the anchor. /// 1. If we have an anchor stored in the player prefs/ anchor store, use that /// 2. If we don't have spatial mapping, just use where the object happens to be /// 3. if we do have spatial mapping, anchor at a vertex dense portion of spatial mapping /// アンカーを設定するのに適した位置を見つけます。 /// 1.アンカーがプレイヤーの環境設定/アンカーストアに格納されている場合は、それを使います。 /// 2.空間マッピングがない場合は、オブジェクトがどこに存在するかを使用するだけです。 /// 3.空間マッピングを行うと、空間マッピングの頂点密度の高い部分にアンカーする。 /// </summary> private void FindAnchorPosition() { // 1. recover a stored anchor if we can // 1.可能であれば、格納されたアンカーを復元する if (PlayerPrefs.HasKey(SavedAnchorKey) && AttachToCachedAnchor(PlayerPrefs.GetString(SavedAnchorKey))) { // SavedAnchorNameのハッシュキーで検索し、キャッシュ済みのアンカーが存在する場合 // キャッシュ済みのアンカー名を取得する。 exportingAnchorName = PlayerPrefs.GetString(SavedAnchorKey); Debug.Log("found " + exportingAnchorName + " again"); // アンカーをバイト配列にエクスポートする。 ExportAnchor(); } // 2. just use the current object position if we don't have access to spatial mapping // 2.空間マッピングにアクセスできない場合は、現在のオブジェクト位置を使用します。 else if (spatialMapping == null) { Debug.Log("No spatial mapping..."); // SharedCollectionのインスタンスを持つゲームオブジェクトのワールド位置にアンカーをエクスポートします。 ExportAnchorAtPosition(objectToAnchor.transform.position); } // 3. seek a vertex dense portion of spatial mapping // 3. 空間マッピングの頂点密集部分を求める。 else { // SpatialMappingで保持されている全てのsurfaceObject(サーフェス)を取得する。 ReadOnlyCollection<SpatialMappingSource.SurfaceObject> surfaces = spatialMapping.GetSurfaceObjects(); if (surfaces == null || surfaces.Count == 0) { // SpatialMapping内にsurfaceObject(サーフェス)が見つからなかった場合 // If we aren't getting surfaces we may need to start the observer. // サーフェスを取得していない場合は、オブザーバを開始する必要があります。 if (spatialMapping.IsObserverRunning() == false) { // オブザーバ開始していない場合 // spatialMappingのオブザーバを開始する。 spatialMapping.StartObserver(); // spatialMappingのオブザーバフラグをtrueにする。 StartedObserver = true; } // And try again after the observer has a chance to get an update. // オブザーバが更新した後、再度試してください。 // spatialMappingの更新間隔時間経過後、再びFindAnchorPositionを実行する。 Invoke("FindAnchorPosition", spatialMapping.GetComponent<SpatialMappingObserver>().TimeBetweenUpdates); } else { // SpatialMapping内にsurfaceObject(サーフェス)が見つかった場合 // スタート時からの経過時間を記録しておく float startTime = Time.realtimeSinceStartup; // If we have surfaces, we need to iterate through them to find a dense area // of geometry, which should provide a good spot for an anchor. // サーフェスがある場合は、アンカーに適した場所になるジオメトリの密な領域を見つけるために、 // 反復処理を行う必要があります。 Mesh bestMesh = null; MeshFilter bestFilter = null; int mostVerts = 0; for (int index = 0; index < surfaces.Count; index++) { // If the current surface doesn't have a filter or a mesh, skip to the next one // This happens as a surface is being processed. We need to track both the mesh // and the filter because the mesh has the verts in local space and the filter has the transform to // world space. // 現在のサーフェスにフィルタまたはメッシュがない場合は、次のフィルタにスキップします。 // フィルタまたはメッシュがないサーフェスはサーフェスの処理中に発生します。 // メッシュにはローカル空間に頂点数があり、フィルタにはワールド座標への変換があるため、 // メッシュとフィルタの両方を追跡する必要があります。 // フィルタを取得します。 MeshFilter currentFilter = surfaces[index].Filter; if (currentFilter == null) { // フィルタがない場合 continue; } // メッシュを取得します。 Mesh currentMesh = currentFilter.sharedMesh; if (currentMesh == null) { // メッシュがない場合 continue; } // If we have a collider we can use the extents to estimate the volume. // サーフェスがコライダーを持っていれば、 // エクステントを使ってサーフェスの大きさ(ベクトルの長さ)を見積もることができます。 MeshCollider currentCollider = surfaces[index].Collider; float volume = currentCollider == null ? 1.0f : currentCollider.bounds.extents.magnitude; // get th verts divided by the volume if any // もしコライダ―があれば、その大きさで割ったメッシュの頂点数(verts)を取得します。 // 1.0fベクトル辺りの頂点数を取得することになります。 int meshVerts = (int)(currentMesh.vertexCount / volume); // and if this is most verts/volume we've seen, record this mesh as the current best. // 1.0fベクトル辺りの頂点数が最も多い場合は、このメッシュを現在のベストメッシュとして記録します。 mostVerts = Mathf.Max(meshVerts, mostVerts); if (mostVerts == meshVerts) { // ベストメッシュの場合、メッシュとフィルタを記録する。 bestMesh = currentMesh; bestFilter = currentFilter; } } // If we have a good area to use, then use it. // 使用する領域が十分あれば、それを使用してください。 if (bestMesh != null && mostVerts > 100) { // ベストメッシュが持つ頂点数が100を超える場合 // Get the average of the vertices // メッシュに含まれる全ての頂点の平均値を取得する。 Vector3[] verts = bestMesh.vertices; Vector3 avgVert = verts.Average(); // transform the average into world space. // 取得した平均値をローカル座標からワールド座標へTransformPointで変換する。 // 計算したワールド座標位置が最も頂点密度の高い座標になる。 Vector3 center = bestFilter.transform.TransformPoint(avgVert); Debug.LogFormat("found a good mesh mostVerts = {0} processed {1} meshes in {2} ms", mostVerts, surfaces.Count, 1000 * (Time.realtimeSinceStartup - startTime)); // then export the anchor where we've calculated. // 計算したアンカーをエクスポートします。 ExportAnchorAtPosition(center); } else { // ベストメッシュが持つ頂点数が100以下の場合 // If we didn't find a good mesh, try again a little later. // 良いメッシュが見つからなかった場合はもう一度やり直してください。 Debug.LogFormat("Failed to find a good mesh mostVerts = {0} processed {1} meshes in {2} ms", mostVerts, surfaces.Count, 1000 * (Time.realtimeSinceStartup - startTime)); // spatialMappingの更新間隔時間経過後、再びFindAnchorPositionを実行する。 Invoke("FindAnchorPosition", spatialMapping.GetComponent<SpatialMappingObserver>().TimeBetweenUpdates); } } } }