注意

Mixed Reality Academy チュートリアルは、HoloLens (第1世代) と Mixed Reality イマーシブヘッドセットを念頭に置いて設計されています。The Mixed Reality Academy tutorials were designed with HoloLens (1st gen) and Mixed Reality Immersive Headsets in mind. そのため、これらのデバイスの開発に関するガイダンスをまだ探している開発者には、これらのチュートリアルを残しておくことが重要です。As such, we feel it is important to leave these tutorials in place for developers who are still looking for guidance in developing for those devices. これらのチュートリアルは いない 最新のツールセットや相互作用が使用されている HoloLens 2 で更新されます。These tutorials will not be updated with the latest toolsets or interactions being used for HoloLens 2. サポートされているデバイスでの作業を続行するために管理されます。They will be maintained to continue working on the supported devices. 今後、HoloLens 2 向けの開発方法を示す新しい一連のチュートリアルが掲載されています。There will be a new series of tutorials that will be posted in the future that will demonstrate how to develop for HoloLens 2. この通知は、これらのチュートリアルが投稿されたときのリンクと共に更新されます。This notice will be updated with a link to those tutorials when they are posted.


MR 空間 230:空間マッピングMR Spatial 230: Spatial mapping

空間マッピングは、環境に関するホログラムを教えることによって、現実世界と仮想世界を組み合わせたものです。Spatial mapping combines the real world and virtual world together by teaching holograms about the environment. MR 空間 230 (Project Planetarium) では、次の方法を学習します。In MR Spatial 230 (Project Planetarium) we'll learn how to:

  • 環境をスキャンし、HoloLens から開発用コンピューターにデータを転送します。Scan the environment and transfer data from the HoloLens to your development machine.
  • シェーダーを調査し、それらを使用してスペースを視覚化する方法を学習します。Explore shaders and learn how to use them for visualizing your space.
  • メッシュ処理を使用して、ルームメッシュを単純な平面に分割します。Break down the room mesh into simple planes using mesh processing.
  • MR の基本 101で学習した配置手法を超えて、その環境にホログラムを配置できる場所についてのフィードバックを提供します。Go beyond the placement techniques we learned in MR Basics 101, and provide feedback about where a hologram can be placed in the environment.
  • 遮蔽効果を調べると、ホログラムが実際のオブジェクトの背後にある場合でも、x 光線による視覚エフェクトを見ることができます。Explore occlusion effects, so when your hologram is behind a real-world object, you can still see it with x-ray vision!

デバイスのサポートDevice support

までCourse HoloLensHoloLens イマーシブ ヘッドセットImmersive headsets
MR 空間 230:空間マッピングMR Spatial 230: Spatial mapping ✔️✔️

開始前の準備Before you start

必須コンポーネントPrerequisites

プロジェクトファイルProject files

  • プロジェクトに必要なファイルをダウンロードします。Download the files required by the project. Unity 2017.2 以降が必要です。 Requires Unity 2017.2 or later.
    • 引き続き Unity 5.6 のサポートが必要な場合は、このリリースをご利用ください。If you still need Unity 5.6 support, please use this release.
    • 引き続き Unity 5.5 のサポートが必要な場合は、このリリースをご利用ください。If you still need Unity 5.5 support, please use this release.
    • 引き続き Unity 5.4 のサポートが必要な場合は、このリリースをご利用ください。If you still need Unity 5.4 support, please use this release.
  • ファイルをデスクトップまたはその他の簡単な場所に保管します。Un-archive the files to your desktop or other easy to reach location.

注意

ダウンロードする前にソースコードを確認する場合は、GitHub から入手できます。If you want to look through the source code before downloading, it's available on GitHub.

メモNotes

  • コード内のブレークポイントにヒットするには、Visual Studio の [マイコードのみの有効化] を無効 (オフ> >) にする必要があります。"Enable Just My Code" in Visual Studio needs to be disabled (unchecked) under Tools > Options > Debugging in order to hit breakpoints in your code.

Unity のセットアップUnity setup

  • Unityを起動します。Start Unity.
  • [新規] を選択して、新しいプロジェクトを作成します。Select New to create a new project.
  • プロジェクトにPlanetariumという名前を指定します。Name the project Planetarium.
  • 3dの設定が選択されていることを確認します。Verify that the 3D setting is selected.
  • [プロジェクトの作成] をクリックします。Click Create Project.
  • Unity が起動したら、[ Edit > Project Settings > Player] を開きます。Once Unity launches, go to Edit > Project Settings > Player.
  • [インスペクター ] パネルで、緑色のWindows ストアアイコンを見つけて選択します。In the Inspector panel, find and select the green Windows Store icon.
  • [その他の設定] を展開します。Expand Other Settings.
  • [レンダリング] セクションで、[ Virtual Reality サポート] オプションをオンにします。In the Rendering section, check the Virtual Reality Supported option.
  • Virtual Reality sdkの一覧に [ Windows Holographic ] が表示されていることを確認します。Verify that Windows Holographic appears in the list of Virtual Reality SDKs. 表示されてい + ない場合は、一覧の下部にあるボタンを選択し、[ Windows Holographic] を選択します。If not, select the + button at the bottom of the list and choose Windows Holographic.
  • [発行の設定] を展開します。Expand Publishing Settings.
  • [機能] セクションで、次の設定を確認します。In the Capabilities section, check the following settings:
    • InternetClientServerInternetClientServer
    • PrivateNetworkClientServerPrivateNetworkClientServer
    • マイクMicrophone
    • SpatialPerceptionSpatialPerception
  • プロジェクト設定の編集 > > 品質に進むGo to Edit > Project Settings > Quality
  • [インスペクター ] パネルの [Windows ストア] アイコンで、[既定] 行の下にある黒のドロップダウン矢印を選択し、既定の設定を [非常に低い] に変更します。In the Inspector panel, under the Windows Store icon, select the black drop-down arrow under the 'Default' row and change the default setting to Very Low.
  • [アセット] [ > カスタムパッケージ] の [パッケージのインポート >] にアクセスします。Go to Assets > Import Package > Custom Package.
  • ..\Holographicacademy-holograms-230-SpatialMapping\Startingフォルダーに移動します。Navigate to the ...\HolographicAcademy-Holograms-230-SpatialMapping\Starting folder.
  • Planetarium unitypackageをクリックします。Click on Planetarium.unitypackage.
  • [開く] をクリックします。Click Open.
  • [ Unity パッケージのインポート] ウィンドウが表示されたら、[インポート] ボタンをクリックします。An Import Unity Package window should appear, click on the Import button.
  • Unity によって、このプロジェクトを完了するために必要なすべての資産がインポートされるのを待ちます。Wait for Unity to import all of the assets that we will need to complete this project.
  • [階層] パネルで、メインカメラを削除します。In the Hierarchy panel, delete the Main Camera.
  • [プロジェクト] パネルの [ HoloToolkit-SpatialMapping-230\Utilities\Prefabs ] フォルダーで、メインカメラオブジェクトを見つけます。In the Project panel, HoloToolkit-SpatialMapping-230\Utilities\Prefabs folder, find the Main Camera object.
  • メインカメラprefab を [階層] パネルにドラッグアンドドロップします。Drag and drop the Main Camera prefab into the Hierarchy panel.
  • [階層] パネルで、指向性ライトオブジェクトを削除します。In the Hierarchy panel, delete the Directional Light object.
  • [プロジェクト] パネルの [ホログラム] フォルダーで、 Cursorオブジェクトを探します。In the Project panel, Holograms folder, locate the Cursor object.
  • カーソルをドラッグして階層に & ドロップします。Drag & drop the Cursor prefab into the Hierarchy.
  • [階層] パネルで、カーソルオブジェクトを選択します。In the Hierarchy panel, select the Cursor object.
  • [インスペクター ] パネルで、[レイヤー ] ドロップダウンをクリックし、[レイヤーの編集... ] を選択します。In the Inspector panel, click the Layer drop-down and select Edit Layers....
  • ユーザーレイヤー 31を "SpatialMapping" として指定します。Name User Layer 31 as "SpatialMapping".
  • 新しいシーンを保存します。ファイル > シーンに名前を付けて保存...Save the new scene: File > Save Scene As...
  • [新しいフォルダー ] をクリックし、フォルダーに「シーン」という名前を指定します。Click New Folder and name the folder Scenes.
  • ファイルに "Planetarium" という名前を付けて、[シーン] フォルダーに保存します。Name the file "Planetarium" and save it in the Scenes folder.

第1章-スキャンChapter 1 - Scanning

事項Objectives

  • SurfaceObserver とその設定がエクスペリエンスとパフォーマンスに与える影響について説明します。Learn about the SurfaceObserver and how its settings impact experience and performance.
  • 部屋のメッシュを収集するためのルームスキャンエクスペリエンスを作成します。Create a room scanning experience to collect the meshes of your room.

マニュアルInstructions

  • [プロジェクト] パネルのHoloToolkit-SpatialMapping-230\SpatialMapping\Prefabsフォルダーで、 SpatialMapping prefab を見つけます。In the Project panel HoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs folder, find the SpatialMapping prefab.
  • SpatialMapping prefab を [階層] パネルにドラッグ & ドロップします。Drag & drop the SpatialMapping prefab into the Hierarchy panel.

ビルドと配置 (パート 1)Build and Deploy (part 1)

  • Unity で、[ファイル > ビルド設定] を選択します。In Unity, select File > Build Settings.
  • [開いているシーンの追加] をクリックして、 Planetariumシーンをビルドに追加します。Click Add Open Scenes to add the Planetarium scene to the build.
  • [プラットフォーム] ボックスの一覧の [ユニバーサル Windows プラットフォーム] を選択し、[プラットフォームの切り替え] をクリックします。Select Universal Windows Platform in the Platform list and click Switch Platform.
  • SDKUniversal 10に設定し、 UWP ビルドの種類D3Dに設定します。Set SDK to Universal 10 and UWP Build Type to D3D.
  • Unity C#プロジェクトを確認します。Check Unity C# Projects.
  • [Build] をクリックします。Click Build.
  • "App" という名前の新しいフォルダーを作成します。Create a New Folder named "App".
  • アプリフォルダーをシングルクリックします。Single click the App folder.
  • [フォルダーの選択] ボタンをクリックします。Press the Select Folder button.
  • Unity のビルドが完了すると、エクスプローラーウィンドウが表示されます。When Unity is done building, a File Explorer window will appear.
  • アプリフォルダーをダブルクリックして開きます。Double-click on the App folder to open it.
  • Planetariumをダブルクリックして、Visual Studio でプロジェクトを読み込みます。Double-click on Planetarium.sln to load the project in Visual Studio.
  • Visual Studio で、上部のツールバーを使用して、構成を [リリース] に変更します。In Visual Studio, use the top toolbar to change the Configuration to Release.
  • プラットフォームをx86に変更します。Change the Platform to x86.
  • [ローカルコンピューター] の右側にあるドロップダウン矢印をクリックし、[リモートコンピューター] を選択します。Click on the drop-down arrow to the right of 'Local Machine', and select Remote Machine.
  • [アドレス] フィールドにデバイスの IP アドレスを入力し、[認証モード] を [ユニバーサル (暗号化されていないプロトコル)] に変更します。Enter your device's IP address in the Address field and change Authentication Mode to Universal (Unencrypted Protocol).
  • [デバッグ]、[デバッグなしで開始] の順にクリック > 、Ctrl キーを押しながら F5キーを押します。Click Debug -> Start Without debugging or press Ctrl + F5.
  • ビルドと配置の状態については、Visual Studio の [出力] パネルをご覧ください。Watch the Output panel in Visual Studio for build and deploy status.
  • アプリがデプロイされたら、ルームを案内します。Once your app has deployed, walk around the room. 周囲のサーフェスが黒と白のワイヤーフレームメッシュで覆われていることがわかります。You will see the surrounding surfaces covered by black and white wireframe meshes.
  • 周囲をスキャンします。Scan your surroundings. 壁、雲、床を見てください。Be sure to look at walls, ceilings, and floors.

ビルドと配置 (パート 2)Build and Deploy (part 2)

次に、空間マッピングがパフォーマンスに与える影響について説明します。Now let's explore how Spatial Mapping can affect performance.

  • Unity で、[ウィンドウ > プロファイラー] を選択します。In Unity, select Window > Profiler.
  • [プロファイラーの追加 > GPU] をクリックします。Click Add Profiler > GPU.
  • [アクティブなプロファイラー の > ] をクリックします。Click Active Profiler > .
  • HoloLens のIP アドレスを入力します。Enter the IP address of your HoloLens.
  • [接続] をクリックします。Click Connect.
  • GPU がフレームをレンダリングするためにかかる時間 (ミリ秒単位) を観察します。Observe the number of milliseconds it takes for the GPU to render a frame.
  • デバイスでアプリケーションの実行を停止します。Stop the application from running on the device.
  • Visual Studio に戻り、 SpatialMappingObserver.csを開きます。Return to Visual Studio and open SpatialMappingObserver.cs. これは、HoloToolkit\SpatialMapping (ユニバーサル Windows) プロジェクトの [] フォルダーにあります。You will find it in the HoloToolkit\SpatialMapping folder of the Assembly-CSharp (Universal Windows) project.
  • 起動中 () 関数を見つけて、次のコード行を追加します。TrianglesPerCubicMeter = 1200;Find the Awake() function, and add the following line of code: TrianglesPerCubicMeter = 1200;
  • プロジェクトをデバイスに再配置してから、プロファイラーを再接続します。Re-deploy the project to your device, and then reconnect the profiler. フレームをレンダリングするミリ秒数の変化を確認します。Observe the change in the number of milliseconds to render a frame.
  • デバイスでアプリケーションの実行を停止します。Stop the application from running on the device.

Unity での保存と読み込みSave and load in Unity

最後に、部屋のメッシュを保存し、Unity に読み込んでみましょう。Finally, let's save our room mesh and load it into Unity.

  • Visual Studio に戻り、前のセクションでTrianglesPerCubicMeter () 関数に追加した行を削除します。Return to Visual Studio and remove the TrianglesPerCubicMeter line that you added in the Awake() function during the previous section.
  • プロジェクトをデバイスに再デプロイします。Redeploy the project to your device. ここでは、3つ500の3つのトライアングルメーターで実行されるようになりました。We should now be running with 500 triangles per cubic meter.
  • ブラウザーを開き、HoloLens IPAddress に「」と入力して、 Windows デバイスポータルに移動します。Open a browser and enter in your HoloLens IPAddress to navigate to the Windows Device Portal.
  • 左側のパネルで [ 3D 表示] オプションを選択します。Select the 3D View option in the left panel.
  • [ Surface 再構築] で、[更新] ボタンを選択します。Under Surface reconstruction select the Update button.
  • HoloLens でスキャンした領域が表示ウィンドウに表示されていることを確認します。Watch as the areas that you have scanned on your HoloLens appear in the display window.
  • 部屋のスキャンを保存するには、[保存] ボタンを押します。To save your room scan, press the Save button.
  • ダウンロードフォルダーを開いて、保存されているルームモデルsrmesh. .objを見つけます。Open your Downloads folder to find the saved room model SRMesh.obj.
  • Unity プロジェクトのAssetsフォルダーにsrmesh. .objをコピーします。Copy SRMesh.obj to the Assets folder of your Unity project.
  • Unity で、[階層] パネルの [ SpatialMapping ] オブジェクトを選択します。In Unity, select the SpatialMapping object in the Hierarchy panel.
  • オブジェクトサーフェイスのオブザーバー (スクリプト) コンポーネントを見つけます。Locate the Object Surface Observer (Script) component.
  • [ルームモデル] プロパティの右側にある円をクリックします。Click the circle to the right of the Room Model property.
  • Srmeshオブジェクトを探して選択し、ウィンドウを閉じます。Find and select the SRMesh object and then close the window.
  • [インスペクター ] パネルの [ルームモデル] プロパティが [ srmesh] に設定されていることを確認します。Verify that the Room Model property in the Inspector panel is now set to SRMesh.
  • [再生] ボタンを押して Unity のプレビューモードに入ります。Press the Play button to enter Unity's preview mode.
  • SpatialMapping コンポーネントは、保存された部屋モデルからメッシュを読み込み、Unity で使用できるようにします。The SpatialMapping component will load the meshes from the saved room model so you can use them in Unity.
  • [シーン] ビューに切り替えて、ワイヤーフレームシェーダーで表示されているすべての部屋モデルを表示します。Switch to Scene view to see all of your room model displayed with the wireframe shader.
  • プレビューモードを終了するには、もう一度 [再生] ボタンをクリックします。Press the Play button again to exit preview mode.

注: Unity で次にプレビューモードに入ると、既定で保存されているルームメッシュが読み込まれます。NOTE: The next time that you enter preview mode in Unity, it will load the saved room mesh by default.

第2章-視覚化Chapter 2 - Visualization

事項Objectives

  • シェーダーの基本について説明します。Learn the basics of shaders.
  • 周囲を視覚化します。Visualize your surroundings.

マニュアルInstructions

  • Unity の [階層] パネルで、 SpatialMappingオブジェクトを選択します。In Unity's Hierarchy panel, select the SpatialMapping object.
  • [インスペクター ] パネルで、[空間マッピングマネージャー (スクリプト) ] コンポーネントを見つけます。In the Inspector panel, find the Spatial Mapping Manager (Script) component.
  • [ Surface Material ] プロパティの右側にある円をクリックします。Click the circle to the right of the Surface Material property.
  • BlueLinesOnWalls素材を検索して選択し、ウィンドウを閉じます。Find and select the BlueLinesOnWalls material and close the window.
  • [プロジェクトパネルシェーダー ] フォルダーで、 BlueLinesOnWallsをダブルクリックして、Visual Studio でシェーダーを開きます。In the Project panel Shaders folder, double-click on BlueLinesOnWalls to open the shader in Visual Studio.
  • これは単純なピクセル (頂点からフラグメント) シェーダーで、次のタスクを実行します。This is a simple pixel (vertex to fragment) shader, which accomplishes the following tasks:
    1. 頂点の位置をワールド空間に変換します。Converts a vertex's location to world space.
    2. ピクセルが垂直かどうかを判断するために、頂点の法線をチェックします。Checks the vertex's normal to determine if a pixel is vertical.
    3. 表示するピクセルの色を設定します。Sets the color of the pixel for rendering.

ビルドと配置Build and Deploy

  • Unity に戻り、[ Play ] を押してプレビューモードに入ります。Return to Unity and press Play to enter preview mode.
  • 青色の線は、保存されているスキャンデータから自動的に読み込まれる、部屋メッシュのすべての垂直サーフェイスに表示されます。Blue lines will be rendered on all vertical surfaces of the room mesh (which automatically loaded from our saved scanning data).
  • [シーン] タブに切り替えて、部屋の表示を調整し、Unity でのルームメッシュ全体の表示方法を確認します。Switch to the Scene tab to adjust your view of the room and see how the entire room mesh appears in Unity.
  • [プロジェクト] パネルで、[素材] フォルダーを見つけて、 BlueLinesOnWallsの素材を選択します。In the Project panel, find the Materials folder and select the BlueLinesOnWalls material.
  • 一部のプロパティを変更し、Unity エディターで変更がどのように表示されるかを確認します。Modify some properties and see how the changes appear in the Unity editor.
    • [インスペクター ] パネルで、 LineScaleの値を調整して、線が太くまたは細くなるようにします。In the Inspector panel, adjust the LineScale value to make the lines appear thicker or thinner.
    • [インスペクター ] パネルで、[行perメーター ] の値を調整して、各壁面に表示される線の数を変更します。In the Inspector panel, adjust the LinesPerMeter value to change how many lines appear on each wall.
  • [再生] をもう一度クリックして、プレビューモードを終了します。Click Play again to exit preview mode.
  • HoloLens にビルドして展開し、シェーダーのレンダリングが実際のサーフェイスにどのように表示されるかを観察します。Build and deploy to the HoloLens and observe how the shader rendering appears on real surfaces.

Unity は、素材をプレビューするための優れたジョブを行いますが、常にデバイスでのレンダリングをチェックアウトすることをお勧めします。Unity does a great job of previewing materials, but it's always a good idea to check-out rendering in the device.

第3章: 処理Chapter 3 - Processing

事項Objectives

  • アプリケーションで使用するために空間マッピングデータを処理する方法について説明します。Learn techniques to process spatial mapping data for use in your application.
  • 空間マッピングデータを分析してプレーンを検索し、三角形を削除します。Analyze spatial mapping data to find planes and remove triangles.
  • ホログラムの配置に平面を使用します。Use planes for hologram placement.

マニュアルInstructions

  • Unity の [プロジェクト] パネルの [ホログラム] フォルダーで、 SpatialProcessingオブジェクトを見つけます。In Unity's Project panel, Holograms folder, find the SpatialProcessing object.
  • SpatialProcessingオブジェクトを [階層] パネルにドラッグ & ドロップします。Drag & drop the SpatialProcessing object into the Hierarchy panel.

SpatialProcessing prefab には、空間マッピングデータを処理するためのコンポーネントが含まれています。The SpatialProcessing prefab includes components for processing the spatial mapping data. SurfaceMeshesToPlanes.csは、空間マッピングのデータに基づいてプレーンを検索して生成します。SurfaceMeshesToPlanes.cs will find and generate planes based on the spatial mapping data. このアプリケーションでは、壁、床、および雲を表す平面を使用します。We will use planes in our application to represent walls, floors and ceilings. この事前 fab には、空間マッピングメッシュから頂点を削除できるRemoveSurfaceVertices.csも含まれています。This prefab also includes RemoveSurfaceVertices.cs which can remove vertices from the spatial mapping mesh. これを使用すると、メッシュに穴を作成したり、不要になった余分な三角形を削除したりできます (プレーンを代わりに使用できるため)。This can be used to create holes in the mesh, or to remove excess triangles that are no longer needed (because planes can be used instead).

  • Unity の [プロジェクト] パネルの [ホログラム] フォルダーで、[場所 ] オブジェクトを見つけます。In Unity's Project panel, Holograms folder, find the SpaceCollection object.
  • [移動 ] オブジェクトを[階層] パネルにドラッグアンドドロップします。Drag and drop the SpaceCollection object into the Hierarchy panel.
  • [階層] パネルで、 SpatialProcessingオブジェクトを選択します。In the Hierarchy panel, select the SpatialProcessing object.
  • [インスペクター ] パネルで、[ Play Space Manager (スクリプト) ] コンポーネントを見つけます。In the Inspector panel, find the Play Space Manager (Script) component.
  • PlaySpaceManager.csをダブルクリックして、Visual Studio で開きます。Double-click on PlaySpaceManager.cs to open it in Visual Studio.

PlaySpaceManager.cs には、アプリケーション固有のコードが含まれています。PlaySpaceManager.cs contains application-specific code. このスクリプトに機能を追加して、次の動作を有効にします。We will add functionality to this script to enable the following behavior:

  1. スキャンの制限時間 (10 秒) を超えた後に、空間マッピングデータの収集を停止します。Stop collecting spatial mapping data after we exceed the scanning time limit (10 seconds).
  2. 空間マッピングデータを処理します。Process the spatial mapping data:
    1. SurfaceMeshesToPlanes を使用して、ワールドの表現を平面 (壁面、床、雲など) として簡単に作成できます。Use SurfaceMeshesToPlanes to create a simpler representation of the world as planes (walls, floors, ceilings, etc).
    2. 平面の境界内にある表面の三角形を削除するには、RemoveSurfaceVertices を使用します。Use RemoveSurfaceVertices to remove surface triangles that fall within plane boundaries.
  3. 世界中にホログラムのコレクションを生成し、ユーザーの近くにある壁と床面に配置します。Generate a collection of holograms in the world and place them on wall and floor planes near the user.

PlaySpaceManager.cs でマークされたコーディング演習を完了するか、次のスクリプトを完成したソリューションに置き換えます。Complete the coding exercises marked in PlaySpaceManager.cs, or replace the script with the finished solution from below:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows.Speech;
using Academy.HoloToolkit.Unity;

/// <summary>
/// The SurfaceManager class allows applications to scan the environment for a specified amount of time 
/// and then process the Spatial Mapping Mesh (find planes, remove vertices) after that time has expired.
/// </summary>
public class PlaySpaceManager : Singleton<PlaySpaceManager>
{
    [Tooltip("When checked, the SurfaceObserver will stop running after a specified amount of time.")]
    public bool limitScanningByTime = true;

    [Tooltip("How much time (in seconds) that the SurfaceObserver will run after being started; used when 'Limit Scanning By Time' is checked.")]
    public float scanTime = 30.0f;

    [Tooltip("Material to use when rendering Spatial Mapping meshes while the observer is running.")]
    public Material defaultMaterial;

    [Tooltip("Optional Material to use when rendering Spatial Mapping meshes after the observer has been stopped.")]
    public Material secondaryMaterial;

    [Tooltip("Minimum number of floor planes required in order to exit scanning/processing mode.")]
    public uint minimumFloors = 1;

    [Tooltip("Minimum number of wall planes required in order to exit scanning/processing mode.")]
    public uint minimumWalls = 1;

    /// <summary>
    /// Indicates if processing of the surface meshes is complete.
    /// </summary>
    private bool meshesProcessed = false;

    /// <summary>
    /// GameObject initialization.
    /// </summary>
    private void Start()
    {
        // Update surfaceObserver and storedMeshes to use the same material during scanning.
        SpatialMappingManager.Instance.SetSurfaceMaterial(defaultMaterial);

        // Register for the MakePlanesComplete event.
        SurfaceMeshesToPlanes.Instance.MakePlanesComplete += SurfaceMeshesToPlanes_MakePlanesComplete;
    }

    /// <summary>
    /// Called once per frame.
    /// </summary>
    private void Update()
    {
        // Check to see if the spatial mapping data has been processed
        // and if we are limiting how much time the user can spend scanning.
        if (!meshesProcessed && limitScanningByTime)
        {
            // If we have not processed the spatial mapping data
            // and scanning time is limited...

            // Check to see if enough scanning time has passed
            // since starting the observer.
            if (limitScanningByTime && ((Time.time - SpatialMappingManager.Instance.StartTime) < scanTime))
            {
                // If we have a limited scanning time, then we should wait until
                // enough time has passed before processing the mesh.
            }
            else
            {
                // The user should be done scanning their environment,
                // so start processing the spatial mapping data...

                /* TODO: 3.a DEVELOPER CODING EXERCISE 3.a */

                // 3.a: Check if IsObserverRunning() is true on the
                // SpatialMappingManager.Instance.
                if(SpatialMappingManager.Instance.IsObserverRunning())
                {
                    // 3.a: If running, Stop the observer by calling
                    // StopObserver() on the SpatialMappingManager.Instance.
                    SpatialMappingManager.Instance.StopObserver();
                }

                // 3.a: Call CreatePlanes() to generate planes.
                CreatePlanes();

                // 3.a: Set meshesProcessed to true.
                meshesProcessed = true;
            }
        }
    }

    /// <summary>
    /// Handler for the SurfaceMeshesToPlanes MakePlanesComplete event.
    /// </summary>
    /// <param name="source">Source of the event.</param>
    /// <param name="args">Args for the event.</param>
    private void SurfaceMeshesToPlanes_MakePlanesComplete(object source, System.EventArgs args)
    {
        /* TODO: 3.a DEVELOPER CODING EXERCISE 3.a */

        // Collection of floor and table planes that we can use to set horizontal items on.
        List<GameObject> horizontal = new List<GameObject>();

        // Collection of wall planes that we can use to set vertical items on.
        List<GameObject> vertical = new List<GameObject>();

        // 3.a: Get all floor and table planes by calling
        // SurfaceMeshesToPlanes.Instance.GetActivePlanes().
        // Assign the result to the 'horizontal' list.
        horizontal = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Table | PlaneTypes.Floor);

        // 3.a: Get all wall planes by calling
        // SurfaceMeshesToPlanes.Instance.GetActivePlanes().
        // Assign the result to the 'vertical' list.
        vertical = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Wall);

        // Check to see if we have enough horizontal planes (minimumFloors)
        // and vertical planes (minimumWalls), to set holograms on in the world.
        if (horizontal.Count >= minimumFloors && vertical.Count >= minimumWalls)
        {
            // We have enough floors and walls to place our holograms on...

            // 3.a: Let's reduce our triangle count by removing triangles
            // from SpatialMapping meshes that intersect with our active planes.
            // Call RemoveVertices().
            // Pass in all activePlanes found by SurfaceMeshesToPlanes.Instance.
            RemoveVertices(SurfaceMeshesToPlanes.Instance.ActivePlanes);

            // 3.a: We can indicate to the user that scanning is over by
            // changing the material applied to the Spatial Mapping meshes.
            // Call SpatialMappingManager.Instance.SetSurfaceMaterial().
            // Pass in the secondaryMaterial.
            SpatialMappingManager.Instance.SetSurfaceMaterial(secondaryMaterial);

            // 3.a: We are all done processing the mesh, so we can now
            // initialize a collection of Placeable holograms in the world
            // and use horizontal/vertical planes to set their starting positions.
            // Call SpaceCollectionManager.Instance.GenerateItemsInWorld().
            // Pass in the lists of horizontal and vertical planes that we found earlier.
            SpaceCollectionManager.Instance.GenerateItemsInWorld(horizontal, vertical);
        }
        else
        {
            // We do not have enough floors/walls to place our holograms on...

            // 3.a: Re-enter scanning mode so the user can find more surfaces by 
            // calling StartObserver() on the SpatialMappingManager.Instance.
            SpatialMappingManager.Instance.StartObserver();

            // 3.a: Re-process spatial data after scanning completes by
            // re-setting meshesProcessed to false.
            meshesProcessed = false;
        }
    }

    /// <summary>
    /// Creates planes from the spatial mapping surfaces.
    /// </summary>
    private void CreatePlanes()
    {
        // Generate planes based on the spatial map.
        SurfaceMeshesToPlanes surfaceToPlanes = SurfaceMeshesToPlanes.Instance;
        if (surfaceToPlanes != null && surfaceToPlanes.enabled)
        {
            surfaceToPlanes.MakePlanes();
        }
    }

    /// <summary>
    /// Removes triangles from the spatial mapping surfaces.
    /// </summary>
    /// <param name="boundingObjects"></param>
    private void RemoveVertices(IEnumerable<GameObject> boundingObjects)
    {
        RemoveSurfaceVertices removeVerts = RemoveSurfaceVertices.Instance;
        if (removeVerts != null && removeVerts.enabled)
        {
            removeVerts.RemoveSurfaceVerticesWithinBounds(boundingObjects);
        }
    }

    /// <summary>
    /// Called when the GameObject is unloaded.
    /// </summary>
    private void OnDestroy()
    {
        if (SurfaceMeshesToPlanes.Instance != null)
        {
            SurfaceMeshesToPlanes.Instance.MakePlanesComplete -= SurfaceMeshesToPlanes_MakePlanesComplete;
        }
    }
}

ビルドと配置Build and Deploy

  • HoloLens に展開する前に、Unity の [再生] ボタンを押して、再生モードにします。Before deploying to the HoloLens, press the Play button in Unity to enter play mode.
  • ファイルからルームメッシュが読み込まれたら、空間マッピングメッシュで処理が開始されるまで10秒間待機します。After the room mesh is loaded from file, wait for 10 seconds before processing starts on the spatial mapping mesh.
  • 処理が完了すると、平面は床、壁面、天井などを表すように見えます。When processing is complete, planes will appear to represent the floor, walls, ceiling, etc.
  • すべての面が見つかったら、カメラの近くにあるフロアのテーブルに太陽のシステムが表示されます。After all of the planes have been found, you should see a solar system appear on a table of floor near the camera.
  • 2つのポスターは、カメラの近くの壁にも表示されます。Two posters should appear on walls near the camera too. ゲームモードで表示できない場合は、[シーン] タブに切り替えます。Switch to the Scene tab if you cannot see them in Game mode.
  • 再生モードを終了するには、もう一度 [再生] ボタンをクリックします。Press the Play button again to exit play mode.
  • 通常どおり HoloLens にビルドしてデプロイします。Build and deploy to the HoloLens, as usual.
  • 空間マッピングデータのスキャンと処理が完了するまで待機します。Wait for scanning and processing of the spatial mapping data to complete.
  • プレーンが表示されたら、太陽のシステムとポスターを検索してみてください。Once you see planes, try to find the solar system and posters in your world.

章 4-配置Chapter 4 - Placement

事項Objectives

  • ホログラムがサーフェイスに適合するかどうかを判断します。Determine if a hologram will fit on a surface.
  • ホログラムがサーフェイスに適合しない場合にユーザーにフィードバックを提供します。Provide feedback to the user when a hologram can/cannot fit on a surface.

マニュアルInstructions

  • Unity の [階層] パネルで、 SpatialProcessingオブジェクトを選択します。In Unity's Hierarchy panel, select the SpatialProcessing object.
  • [インスペクター ] パネルで、[平面 (スクリプト)] コンポーネントの表面メッシュを見つけます。In the Inspector panel, find the Surface Meshes To Planes (Script) component.
  • [描画平面] プロパティを [なし] に変更して選択範囲をクリアします。Change the Draw Planes property to Nothing to clear the selection.
  • "描画平面" プロパティを "" に変更して、壁面平面だけがレンダリングされるようにします。Change the Draw Planes property to Wall, so that only wall planes will be rendered.
  • [プロジェクト] パネルのScriptsフォルダーで、 Placeable.csをダブルクリックして Visual Studio で開きます。In the Project panel, Scripts folder, double-click on Placeable.cs to open it in Visual Studio.

配置可能なスクリプトは、平面検索が完了した後に作成されたポスターとプロジェクションボックスに既にアタッチされています。The Placeable script is already attached to the posters and projection box that are created after plane finding completes. いくつかのコードをコメント解除するだけで、次のことが実現されます。All we need to do is uncomment some code, and this script will achieve the following:

  1. Raycasting が境界キューブの中心と4つのコーナーから表面にホログラムを収めるかどうかを決定します。Determine if a hologram will fit on a surface by raycasting from the center and four corners of the bounding cube.
  2. 表面の法線を調べて、ホログラムをフラッシュするのに十分な滑らかさがあるかどうかを判断します。Check the surface normal to determine if it is smooth enough for the hologram to sit flush on.
  3. ホログラムの周りに境界キューブをレンダリングして、配置中の実際のサイズを示します。Render a bounding cube around the hologram to show its actual size while being placed.
  4. ホログラムの下または後ろに影をキャストして、床と壁のどこに配置されるかを示します。Cast a shadow under/behind the hologram to show where it will be placed on the floor/wall.
  5. ホログラムを表面に配置できない場合、または緑の場合は、影を赤で表示します。Render the shadow as red, if the hologram cannot be placed on the surface, or green, if it can.
  6. 関係がある表面の種類 (垂直方向または水平方向) に合わせてホログラムを再配置します。Re-orient the hologram to align with the surface type (vertical or horizontal) that it has affinity to.
  7. 選択したサーフェイスにホログラムをスムーズに配置して、ジャンプやスナップ動作を回避します。Smoothly place the hologram on the selected surface to avoid jumping or snapping behavior.

以下のコーディング演習ですべてのコードのコメントを解除するか、 Placeable.csでこの完成したソリューションを使用します。Uncomment all code in the coding exercise below, or use this completed solution in Placeable.cs:

using System.Collections.Generic;
using UnityEngine;
using Academy.HoloToolkit.Unity;

/// <summary>
/// Enumeration containing the surfaces on which a GameObject
/// can be placed.  For simplicity of this sample, only one
/// surface type is allowed to be selected.
/// </summary>
public enum PlacementSurfaces
{
    // Horizontal surface with an upward pointing normal.    
    Horizontal = 1,

    // Vertical surface with a normal facing the user.
    Vertical = 2,
}

/// <summary>
/// The Placeable class implements the logic used to determine if a GameObject
/// can be placed on a target surface. Constraints for placement include:
/// * No part of the GameObject's box collider impacts with another object in the scene
/// * The object lays flat (within specified tolerances) against the surface
/// * The object would not fall off of the surface if gravity were enabled.
/// This class also provides the following visualizations.
/// * A transparent cube representing the object's box collider.
/// * Shadow on the target surface indicating whether or not placement is valid.
/// </summary>
public class Placeable : MonoBehaviour
{
    [Tooltip("The base material used to render the bounds asset when placement is allowed.")]
    public Material PlaceableBoundsMaterial = null;

    [Tooltip("The base material used to render the bounds asset when placement is not allowed.")]
    public Material NotPlaceableBoundsMaterial = null;

    [Tooltip("The material used to render the placement shadow when placement it allowed.")]
    public Material PlaceableShadowMaterial = null;

    [Tooltip("The material used to render the placement shadow when placement it not allowed.")]
    public Material NotPlaceableShadowMaterial = null;

    [Tooltip("The type of surface on which the object can be placed.")]
    public PlacementSurfaces PlacementSurface = PlacementSurfaces.Horizontal;

    [Tooltip("The child object(s) to hide during placement.")]
    public List<GameObject> ChildrenToHide = new List<GameObject>();

    /// <summary>
    /// Indicates if the object is in the process of being placed.
    /// </summary>
    public bool IsPlacing { get; private set; }

    // The most recent distance to the surface.  This is used to 
    // locate the object when the user's gaze does not intersect
    // with the Spatial Mapping mesh.
    private float lastDistance = 2.0f;

    // The distance away from the target surface that the object should hover prior while being placed.
    private float hoverDistance = 0.15f;

    // Threshold (the closer to 0, the stricter the standard) used to determine if a surface is flat.
    private float distanceThreshold = 0.02f;

    // Threshold (the closer to 1, the stricter the standard) used to determine if a surface is vertical.
    private float upNormalThreshold = 0.9f;

    // Maximum distance, from the object, that placement is allowed.
    // This is used when raycasting to see if the object is near a placeable surface.
    private float maximumPlacementDistance = 5.0f;

    // Speed (1.0 being fastest) at which the object settles to the surface upon placement.
    private float placementVelocity = 0.06f;

    // Indicates whether or not this script manages the object's box collider.
    private bool managingBoxCollider = false;

    // The box collider used to determine of the object will fit in the desired location.
    // It is also used to size the bounding cube.
    private BoxCollider boxCollider = null;

    // Visible asset used to show the dimensions of the object. This asset is sized
    // using the box collider's bounds.
    private GameObject boundsAsset = null;

    // Visible asset used to show the where the object is attempting to be placed.
    // This asset is sized using the box collider's bounds.
    private GameObject shadowAsset = null;

    // The location at which the object will be placed.
    private Vector3 targetPosition;

    /// <summary>
    /// Called when the GameObject is created.
    /// </summary>
    private void Awake()
    {
        targetPosition = gameObject.transform.position;

        // Get the object's collider.
        boxCollider = gameObject.GetComponent<BoxCollider>();
        if (boxCollider == null)
        {
            // The object does not have a collider, create one and remember that
            // we are managing it.
            managingBoxCollider = true;
            boxCollider = gameObject.AddComponent<BoxCollider>();
            boxCollider.enabled = false;
        }

        // Create the object that will be used to indicate the bounds of the GameObject.
        boundsAsset = GameObject.CreatePrimitive(PrimitiveType.Cube);
        boundsAsset.transform.parent = gameObject.transform;
        boundsAsset.SetActive(false);

        // Create a object that will be used as a shadow.
        shadowAsset = GameObject.CreatePrimitive(PrimitiveType.Quad);
        shadowAsset.transform.parent = gameObject.transform;
        shadowAsset.SetActive(false);
    }

    /// <summary>
    /// Called when our object is selected.  Generally called by
    /// a gesture management component.
    /// </summary>
    public void OnSelect()
    {
        /* TODO: 4.a CODE ALONG 4.a */

        if (!IsPlacing)
        {
            OnPlacementStart();
        }
        else
        {
            OnPlacementStop();
        }
    }

    /// <summary>
    /// Called once per frame.
    /// </summary>
    private void Update()
    {
        /* TODO: 4.a CODE ALONG 4.a */

        if (IsPlacing)
        {
            // Move the object.
            Move();

            // Set the visual elements.
            Vector3 targetPosition;
            Vector3 surfaceNormal;
            bool canBePlaced = ValidatePlacement(out targetPosition, out surfaceNormal);
            DisplayBounds(canBePlaced);
            DisplayShadow(targetPosition, surfaceNormal, canBePlaced);
        }
        else
        {
            // Disable the visual elements.
            boundsAsset.SetActive(false);
            shadowAsset.SetActive(false);

            // Gracefully place the object on the target surface.
            float dist = (gameObject.transform.position - targetPosition).magnitude;
            if (dist > 0)
            {
                gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, targetPosition, placementVelocity / dist);
            }
            else
            {
                // Unhide the child object(s) to make placement easier.
                for (int i = 0; i < ChildrenToHide.Count; i++)
                {
                    ChildrenToHide[i].SetActive(true);
                }
            }
        }
    }

    /// <summary>
    /// Verify whether or not the object can be placed.
    /// </summary>
    /// <param name="position">
    /// The target position on the surface.
    /// </param>
    /// <param name="surfaceNormal">
    /// The normal of the surface on which the object is to be placed.
    /// </param>
    /// <returns>
    /// True if the target position is valid for placing the object, otherwise false.
    /// </returns>
    private bool ValidatePlacement(out Vector3 position, out Vector3 surfaceNormal)
    {
        Vector3 raycastDirection = gameObject.transform.forward;

        if (PlacementSurface == PlacementSurfaces.Horizontal)
        {
            // Placing on horizontal surfaces.
            // Raycast from the bottom face of the box collider.
            raycastDirection = -(Vector3.up);
        }

        // Initialize out parameters.
        position = Vector3.zero;
        surfaceNormal = Vector3.zero;

        Vector3[] facePoints = GetColliderFacePoints();

        // The origin points we receive are in local space and we 
        // need to raycast in world space.
        for (int i = 0; i < facePoints.Length; i++)
        {
            facePoints[i] = gameObject.transform.TransformVector(facePoints[i]) + gameObject.transform.position;
        }

        // Cast a ray from the center of the box collider face to the surface.
        RaycastHit centerHit;
        if (!Physics.Raycast(facePoints[0],
                        raycastDirection,
                        out centerHit,
                        maximumPlacementDistance,
                        SpatialMappingManager.Instance.LayerMask))
        {
            // If the ray failed to hit the surface, we are done.
            return false;
        }

        // We have found a surface.  Set position and surfaceNormal.
        position = centerHit.point;
        surfaceNormal = centerHit.normal;

        // Cast a ray from the corners of the box collider face to the surface.
        for (int i = 1; i < facePoints.Length; i++)
        {
            RaycastHit hitInfo;
            if (Physics.Raycast(facePoints[i],
                                raycastDirection,
                                out hitInfo,
                                maximumPlacementDistance,
                                SpatialMappingManager.Instance.LayerMask))
            {
                // To be a valid placement location, each of the corners must have a similar
                // enough distance to the surface as the center point
                if (!IsEquivalentDistance(centerHit.distance, hitInfo.distance))
                {
                    return false;
                }
            }
            else
            {
                // The raycast failed to intersect with the target layer.
                return false;
            }
        }

        return true;
    }

    /// <summary>
    /// Determine the coordinates, in local space, of the box collider face that 
    /// will be placed against the target surface.
    /// </summary>
    /// <returns>
    /// Vector3 array with the center point of the face at index 0.
    /// </returns>
    private Vector3[] GetColliderFacePoints()
    {
        // Get the collider extents.  
        // The size values are twice the extents.
        Vector3 extents = boxCollider.size / 2;

        // Calculate the min and max values for each coordinate.
        float minX = boxCollider.center.x - extents.x;
        float maxX = boxCollider.center.x + extents.x;
        float minY = boxCollider.center.y - extents.y;
        float maxY = boxCollider.center.y + extents.y;
        float minZ = boxCollider.center.z - extents.z;
        float maxZ = boxCollider.center.z + extents.z;

        Vector3 center;
        Vector3 corner0;
        Vector3 corner1;
        Vector3 corner2;
        Vector3 corner3;

        if (PlacementSurface == PlacementSurfaces.Horizontal)
        {
            // Placing on horizontal surfaces.
            center = new Vector3(boxCollider.center.x, minY, boxCollider.center.z);
            corner0 = new Vector3(minX, minY, minZ);
            corner1 = new Vector3(minX, minY, maxZ);
            corner2 = new Vector3(maxX, minY, minZ);
            corner3 = new Vector3(maxX, minY, maxZ);
        }
        else
        {
            // Placing on vertical surfaces.
            center = new Vector3(boxCollider.center.x, boxCollider.center.y, maxZ);
            corner0 = new Vector3(minX, minY, maxZ);
            corner1 = new Vector3(minX, maxY, maxZ);
            corner2 = new Vector3(maxX, minY, maxZ);
            corner3 = new Vector3(maxX, maxY, maxZ);
        }

        return new Vector3[] { center, corner0, corner1, corner2, corner3 };
    }

    /// <summary>
    /// Put the object into placement mode.
    /// </summary>
    public void OnPlacementStart()
    {
        // If we are managing the collider, enable it. 
        if (managingBoxCollider)
        {
            boxCollider.enabled = true;
        }

        // Hide the child object(s) to make placement easier.
        for (int i = 0; i < ChildrenToHide.Count; i++)
        {
            ChildrenToHide[i].SetActive(false);
        }

        // Tell the gesture manager that it is to assume
        // all input is to be given to this object.
        GestureManager.Instance.OverrideFocusedObject = gameObject;

        // Enter placement mode.
        IsPlacing = true;
    }

    /// <summary>
    /// Take the object out of placement mode.
    /// </summary>
    /// <remarks>
    /// This method will leave the object in placement mode if called while
    /// the object is in an invalid location.  To determine whether or not
    /// the object has been placed, check the value of the IsPlacing property.
    /// </remarks>
    public void OnPlacementStop()
    {
        // ValidatePlacement requires a normal as an out parameter.
        Vector3 position;
        Vector3 surfaceNormal;

        // Check to see if we can exit placement mode.
        if (!ValidatePlacement(out position, out surfaceNormal))
        {
            return;
        }

        // The object is allowed to be placed.
        // We are placing at a small buffer away from the surface.
        targetPosition = position + (0.01f * surfaceNormal);

        OrientObject(true, surfaceNormal);

        // If we are managing the collider, disable it. 
        if (managingBoxCollider)
        {
            boxCollider.enabled = false;
        }

        // Tell the gesture manager that it is to resume
        // its normal behavior.
        GestureManager.Instance.OverrideFocusedObject = null;

        // Exit placement mode.
        IsPlacing = false;
    }

    /// <summary>
    /// Positions the object along the surface toward which the user is gazing.
    /// </summary>
    /// <remarks>
    /// If the user's gaze does not intersect with a surface, the object
    /// will remain at the most recently calculated distance.
    /// </remarks>
    private void Move()
    {
        Vector3 moveTo = gameObject.transform.position;
        Vector3 surfaceNormal = Vector3.zero;
        RaycastHit hitInfo;

        bool hit = Physics.Raycast(Camera.main.transform.position,
                                Camera.main.transform.forward,
                                out hitInfo,
                                20f,
                                SpatialMappingManager.Instance.LayerMask);

        if (hit)
        {
            float offsetDistance = hoverDistance;

            // Place the object a small distance away from the surface while keeping 
            // the object from going behind the user.
            if (hitInfo.distance <= hoverDistance)
            {
                offsetDistance = 0f;
            }

            moveTo = hitInfo.point + (offsetDistance * hitInfo.normal);

            lastDistance = hitInfo.distance;
            surfaceNormal = hitInfo.normal;
        }
        else
        {
            // The raycast failed to hit a surface.  In this case, keep the object at the distance of the last
            // intersected surface.
            moveTo = Camera.main.transform.position + (Camera.main.transform.forward * lastDistance);
        }

        // Follow the user's gaze.
        float dist = Mathf.Abs((gameObject.transform.position - moveTo).magnitude);
        gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, moveTo, placementVelocity / dist);

        // Orient the object.
        // We are using the return value from Physics.Raycast to instruct
        // the OrientObject function to align to the vertical surface if appropriate.
        OrientObject(hit, surfaceNormal);
    }

    /// <summary>
    /// Orients the object so that it faces the user.
    /// </summary>
    /// <param name="alignToVerticalSurface">
    /// If true and the object is to be placed on a vertical surface, 
    /// orient parallel to the target surface.  If false, orient the object 
    /// to face the user.
    /// </param>
    /// <param name="surfaceNormal">
    /// The target surface's normal vector.
    /// </param>
    /// <remarks>
    /// The aligntoVerticalSurface parameter is ignored if the object
    /// is to be placed on a horizontalSurface
    /// </remarks>
    private void OrientObject(bool alignToVerticalSurface, Vector3 surfaceNormal)
    {
        Quaternion rotation = Camera.main.transform.localRotation;

        // If the user's gaze does not intersect with the Spatial Mapping mesh,
        // orient the object towards the user.
        if (alignToVerticalSurface && (PlacementSurface == PlacementSurfaces.Vertical))
        {
            // We are placing on a vertical surface.
            // If the normal of the Spatial Mapping mesh indicates that the
            // surface is vertical, orient parallel to the surface.
            if (Mathf.Abs(surfaceNormal.y) <= (1 - upNormalThreshold))
            {
                rotation = Quaternion.LookRotation(-surfaceNormal, Vector3.up);
            }
        }
        else
        {
            rotation.x = 0f;
            rotation.z = 0f;
        }

        gameObject.transform.rotation = rotation;
    }

    /// <summary>
    /// Displays the bounds asset.
    /// </summary>
    /// <param name="canBePlaced">
    /// Specifies if the object is in a valid placement location.
    /// </param>
    private void DisplayBounds(bool canBePlaced)
    {
        // Ensure the bounds asset is sized and positioned correctly.
        boundsAsset.transform.localPosition = boxCollider.center;
        boundsAsset.transform.localScale = boxCollider.size;
        boundsAsset.transform.rotation = gameObject.transform.rotation;

        // Apply the appropriate material.
        if (canBePlaced)
        {
            boundsAsset.GetComponent<Renderer>().sharedMaterial = PlaceableBoundsMaterial;
        }
        else
        {
            boundsAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableBoundsMaterial;
        }

        // Show the bounds asset.
        boundsAsset.SetActive(true);
    }

    /// <summary>
    /// Displays the placement shadow asset.
    /// </summary>
    /// <param name="position">
    /// The position at which to place the shadow asset.
    /// </param>
    /// <param name="surfaceNormal">
    /// The normal of the surface on which the asset will be placed
    /// </param>
    /// <param name="canBePlaced">
    /// Specifies if the object is in a valid placement location.
    /// </param>
    private void DisplayShadow(Vector3 position,
                            Vector3 surfaceNormal,
                            bool canBePlaced)
    {
        // Rotate and scale the shadow so that it is displayed on the correct surface and matches the object.
        float rotationX = 0.0f;

        if (PlacementSurface == PlacementSurfaces.Horizontal)
        {
            rotationX = 90.0f;
            shadowAsset.transform.localScale = new Vector3(boxCollider.size.x, boxCollider.size.z, 1);
        }
        else
        {
            shadowAsset.transform.localScale = boxCollider.size;
        }

        Quaternion rotation = Quaternion.Euler(rotationX, gameObject.transform.rotation.eulerAngles.y, 0);
        shadowAsset.transform.rotation = rotation;

        // Apply the appropriate material.
        if (canBePlaced)
        {
            shadowAsset.GetComponent<Renderer>().sharedMaterial = PlaceableShadowMaterial;
        }
        else
        {
            shadowAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableShadowMaterial;
        }

        // Show the shadow asset as appropriate.        
        if (position != Vector3.zero)
        {
            // Position the shadow a small distance from the target surface, along the normal.
            shadowAsset.transform.position = position + (0.01f * surfaceNormal);
            shadowAsset.SetActive(true);
        }
        else
        {
            shadowAsset.SetActive(false);
        }
    }

    /// <summary>
    /// Determines if two distance values should be considered equivalent. 
    /// </summary>
    /// <param name="d1">
    /// Distance to compare.
    /// </param>
    /// <param name="d2">
    /// Distance to compare.
    /// </param>
    /// <returns>
    /// True if the distances are within the desired tolerance, otherwise false.
    /// </returns>
    private bool IsEquivalentDistance(float d1, float d2)
    {
        float dist = Mathf.Abs(d1 - d2);
        return (dist <= distanceThreshold);
    }

    /// <summary>
    /// Called when the GameObject is unloaded.
    /// </summary>
    private void OnDestroy()
    {
        // Unload objects we have created.
        Destroy(boundsAsset);
        boundsAsset = null;
        Destroy(shadowAsset);
        shadowAsset = null;
    }
}

ビルドと配置Build and Deploy

  • 前と同様に、プロジェクトをビルドし、HoloLens にデプロイします。As before, build the project and deploy to the HoloLens.
  • 空間マッピングデータのスキャンと処理が完了するまで待機します。Wait for scanning and processing of the spatial mapping data to complete.
  • 太陽のシステムが表示されたら、下の投影ボックスを見つめ、選択ジェスチャを実行して移動します。When you see the solar system, gaze at the projection box below and perform a select gesture to move it around. [プロジェクション] ボックスを選択すると、[プロジェクション] ボックスの周囲に境界キューブが表示されます。While the projection box is selected, a bounding cube will be visible around the projection box.
  • 部屋の別の場所に移動します。Move you head to gaze at a different location in the room. プロジェクションボックスは、お使いの宝石に従う必要があります。The projection box should follow your gaze. 投影ボックスの下の影が赤に変わったら、その表面にホログラムを配置することはできません。When the shadow below the projection box turns red, you cannot place the hologram on that surface. 投影ボックスの下の影が緑色に変わったら、別の選択ジェスチャを実行してホログラムを配置できます。When the shadow below the projection box turns green, you can place the hologram by performing another select gesture.
  • 壁の holographic ポスターの1つを探して選択し、新しい場所に移動します。Find and select one of the holographic posters on a wall to move it to a new location. フロアまたはシーリングにポスターを配置することはできません。また、移動するたびに各壁面に合わせて正しい向きに保たれることに注意してください。Notice that you cannot place the poster on the floor or ceiling, and that it stays correctly oriented to each wall as you move around.

第5章-オクルージョンChapter 5 - Occlusion

事項Objectives

  • ホログラムが空間マッピングメッシュによって occluded されているかどうかを判断します。Determine if a hologram is occluded by the spatial mapping mesh.
  • さまざまな遮蔽手法を適用して、楽しい効果を実現します。Apply different occlusion techniques to achieve a fun effect.

マニュアルInstructions

まず、空間マッピングメッシュで、occluding を使用せずに他のホログラムを occlude できるようにします。First, we are going to allow the spatial mapping mesh to occlude other holograms without occluding the real world:

  • [階層] パネルで、 SpatialProcessingオブジェクトを選択します。In the Hierarchy panel, select the SpatialProcessing object.
  • [インスペクター ] パネルで、[ Play Space Manager (スクリプト) ] コンポーネントを見つけます。In the Inspector panel, find the Play Space Manager (Script) component.
  • [セカンダリマテリアル] プロパティの右側にある円をクリックします。Click the circle to the right of the Secondary Material property.
  • オクルージョンを検索して選択し、ウィンドウを閉じます。Find and select the Occlusion material and close the window.

次に、地球に特別な動作を追加します。これにより、(太陽などの) 別のホログラムによって occluded されたとき、または空間マッピングメッシュによって、青の強調表示になります。Next, we are going to add a special behavior to Earth, so that it has a blue highlight whenever it becomes occluded by another hologram (like the sun), or by the spatial mapping mesh:

  • [プロジェクト] パネルの [ホログラム] フォルダーで、[ ] オブジェクトを展開します。In the Project panel, in the Holograms folder, expand the SolarSystem object.
  • [地球] をクリックします。Click on Earth.
  • [インスペクター ] パネルで、地球の素材 (下部コンポーネント) を見つけます。In the Inspector panel, find the Earth's material (bottom component).
  • シェーダーのドロップダウンで、シェーダーをカスタム > OcclusionRimに変更します。In the Shader drop-down, change the shader to Custom > OcclusionRim. これは、別のオブジェクトによって occluded されたときに、地球を中心とした青い強調表示になります。This will render a blue highlight around Earth whenever it is occluded by another object.

最後に、太陽のシステムで惑星に対して x 線の視覚効果を有効にします。Finally, we are going to enable an x-ray vision effect for planets in our solar system. 次のことを実現するために、 PlanetOcclusion.cs (スクリプト \ システムフォルダーにあります) を編集する必要があります。We will need to edit PlanetOcclusion.cs (found in the Scripts\SolarSystem folder) in order to achieve the following:

  1. 地球が SpatialMapping レイヤーによって occluded されているかどうかを判断します (部屋メッシュと平面)。Determine if a planet is occluded by the SpatialMapping layer (room meshes and planes).
  2. SpatialMapping レイヤーによって occluded されるたびに、地球のワイヤーフレーム表現を表示します。Show the wireframe representation of a planet whenever it is occluded by the SpatialMapping layer.
  3. 地球のワイヤーフレーム表現が SpatialMapping レイヤーによってブロックされていない場合は非表示にします。Hide the wireframe representation of a planet when it is not blocked by the SpatialMapping layer.

PlanetOcclusion.cs のコーディングの演習に従うか、次の解決策を使用します。Follow the coding exercise in PlanetOcclusion.cs, or use the following solution:

using UnityEngine;
using Academy.HoloToolkit.Unity;

/// <summary>
/// Determines when the occluded version of the planet should be visible.
/// This script allows us to do selective occlusion, so the occlusionObject
/// will only be rendered when a Spatial Mapping surface is occluding the planet,
/// not when another hologram is responsible for the occlusion.
/// </summary>
public class PlanetOcclusion : MonoBehaviour
{
    [Tooltip("Object to display when the planet is occluded.")]
    public GameObject occlusionObject;

    /// <summary>
    /// Points to raycast to when checking for occlusion.
    /// </summary>
    private Vector3[] checkPoints;

    // Use this for initialization
    void Start()
    {
        occlusionObject.SetActive(false);

        // Set the check points to use when testing for occlusion.
        MeshFilter filter = gameObject.GetComponent<MeshFilter>();
        Vector3 extents = filter.mesh.bounds.extents;
        Vector3 center = filter.mesh.bounds.center;
        Vector3 top = new Vector3(center.x, center.y + extents.y, center.z);
        Vector3 left = new Vector3(center.x - extents.x, center.y, center.z);
        Vector3 right = new Vector3(center.x + extents.x, center.y, center.z);
        Vector3 bottom = new Vector3(center.x, center.y - extents.y, center.z);

        checkPoints = new Vector3[] { center, top, left, right, bottom };
    }

    // Update is called once per frame
    void Update()
    {
        /* TODO: 5.a DEVELOPER CODING EXERCISE 5.a */

        // Check to see if any of the planet's boundary points are occluded.
        for (int i = 0; i < checkPoints.Length; i++)
        {
            // 5.a: Convert the current checkPoint to world coordinates.
            // Call gameObject.transform.TransformPoint(checkPoints[i]).
            // Assign the result to a new Vector3 variable called 'checkPt'.
            Vector3 checkPt = gameObject.transform.TransformPoint(checkPoints[i]);

            // 5.a: Call Vector3.Distance() to calculate the distance
            // between the Main Camera's position and 'checkPt'.
            // Assign the result to a new float variable called 'distance'.
            float distance = Vector3.Distance(Camera.main.transform.position, checkPt);

            // 5.a: Take 'checkPt' and subtract the Main Camera's position from it.
            // Assign the result to a new Vector3 variable called 'direction'.
            Vector3 direction = checkPt - Camera.main.transform.position;

            // Used to indicate if the call to Physics.Raycast() was successful.
            bool raycastHit = false;

            // 5.a: Check if the planet is occluded by a spatial mapping surface.
            // Call Physics.Raycast() with the following arguments:
            // - Pass in the Main Camera's position as the origin.
            // - Pass in 'direction' for the direction.
            // - Pass in 'distance' for the maxDistance.
            // - Pass in SpatialMappingManager.Instance.LayerMask as layerMask.
            // Assign the result to 'raycastHit'.
            raycastHit = Physics.Raycast(Camera.main.transform.position, direction, distance, SpatialMappingManager.Instance.LayerMask);

            if (raycastHit)
            {
                // 5.a: Our raycast hit a surface, so the planet is occluded.
                // Set the occlusionObject to active.
                occlusionObject.SetActive(true);

                // At least one point is occluded, so break from the loop.
                break;
            }
            else
            {
                // 5.a: The Raycast did not hit, so the planet is not occluded.
                // Deactivate the occlusionObject.
                occlusionObject.SetActive(false);
            }
        }
    }
}

ビルドと配置Build and Deploy

  • 通常どおり、アプリケーションを HoloLens に構築してデプロイします。Build and deploy the application to HoloLens, as usual.
  • 空間マッピングデータのスキャンと処理が完了するまで待ちます (壁に青い線が表示されます)。Wait for scanning and processing of the spatial mapping data to be complete (you should see blue lines appear on walls).
  • [太陽システムの投影] ボックスを探して選択し、壁の横またはカウンターの背後にあるボックスを設定します。Find and select the solar system's projection box and then set the box next to a wall or behind a counter.
  • [ポスター] ボックスまたは [投影] ボックスで、ピアに対する表面の背後を非表示にすることで、基本的なオクルージョンを表示できます。You can view basic occlusion by hiding behind surfaces to peer at the poster or projection box.
  • 地球を探すには、別のホログラムや表面にいるときは常に青いハイライト効果があるはずです。Look for the Earth, there should be a blue highlight effect whenever it goes behind another hologram or surface.
  • 惑星が部屋の壁またはその他の表面の内側に移動するのを監視します。Watch as the planets move behind the wall or other surfaces in the room. X 線によるビジョンが可能になり、ワイヤーフレームスケルトンを見ることができるようになりました。You now have x-ray vision and can see their wireframe skeletons!

最後ですThe End

おめでとうございます!Congratulations! これで MR 空間230 が完成しました。空間マッピングYou have now completed MR Spatial 230: Spatial mapping.

  • 環境をスキャンして、Unity に空間マッピングデータを読み込む方法について説明します。You know how to scan your environment and load spatial mapping data to Unity.
  • シェーダーの基本と、マテリアルを使用して世界を再視覚化する方法について説明します。You understand the basics of shaders and how materials can be used to re-visualize the world.
  • 平面を検索し、メッシュから三角形を削除するための新しい処理手法について学習しました。You learned of new processing techniques for finding planes and removing triangles from a mesh.
  • 意味のあるサーフェイスにホログラムを移動して配置することができました。You were able to move and place holograms on surfaces that made sense.
  • さまざまな遮蔽手法を経験し、開花の力を活用しました。You experienced different occlusion techniques and harnessed the power of x-ray vision!