230. Пространство в смешанной реальности: пространственное сопоставлениеMR Spatial 230: Spatial mapping

Примечание

Руководства 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.A new series of tutorials has been posted for HoloLens 2.

Пространственное сопоставление объединяет реальный и виртуальный мир вместе с голограммами о среде.Spatial mapping combines the real world and virtual world together by teaching holograms about the environment. В MR-пространственном 230 (Project планетариум) мы расскажем: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.
  • Выйдите за пределы приемов размещения, которые мы узнали в статье об основных 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-ray!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
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.

Установка UnityUnity setup

  • Запустите Unity.Start Unity.
  • Выберите создать, чтобы создать новый проект.Select New to create a new project.
  • Назовите проект планетариум.Name the project Planetarium.
  • Убедитесь, что выбран параметр 3D .Verify that the 3D setting is selected.
  • Нажмите кнопку создать проект.Click Create Project.
  • После запуска Unity перейдите в раздел правка > параметры проекта > 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.
  • В разделе " Подготовка к просмотру " установите флажок поддерживаемый виртуальный реальность .In the Rendering section, check the Virtual Reality Supported option.
  • Убедитесь, что Windows holographic отображается в списке пакетов SDK виртуальной реальности.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:
    • InternetClientServer;InternetClientServer
    • PrivateNetworkClientServer;PrivateNetworkClientServer
    • МикрофонMicrophone
    • SpatialPerception;SpatialPerception
  • Перейдите к разделу изменение > параметры проекта > качество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.
  • Перейдите в папку . ..\холографикакадеми-холограмс-230-спатиалмаппинг\стартинг .Navigate to the ...\HolographicAcademy-Holograms-230-SpatialMapping\Starting folder.
  • Щелкните Планетариум. пакет 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 найдите основной объект Camera .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.
  • На панели проект в папке голограммы выберите объект курсора .In the Project panel, Holograms folder, locate the Cursor object.
  • Перетащите & курсора prefab в иерархию.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 значение "спатиалмаппинг".Name User Layer 31 as "SpatialMapping".
  • Сохраните новую сцену: файл > сохранить сцену как...Save the new scene: File > Save Scene As...
  • Щелкните создать папку и присвойте имя папке сцены.Click New Folder and name the folder Scenes.
  • Назовите файл «планетариум» и сохраните его в папке « сцены ».Name the file "Planetarium" and save it in the Scenes folder.

Глава 1. сканированиеChapter 1 - Scanning

ЦелиObjectives

  • Узнайте о Сурфацеобсервер и о том, как его параметры влияют на работу и производительность.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 на панели проекта найдите спатиалмаппинг prefab.In the Project panel HoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs folder, find the SpatialMapping prefab.
  • Перетащите & спатиалмаппинг prefab на панель Иерархия .Drag & drop the SpatialMapping prefab into the Hierarchy panel.

Сборка и развертывание (часть 1)Build and Deploy (part 1)

  • В Unity выберите файл > параметры сборки.In Unity, select File > Build Settings.
  • Нажмите кнопку Добавить открытые сцены , чтобы добавить сцену планетариум в сборку.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.
  • Установите для пакета SDK значение универсальной 10 , а для типа сборки UWPD3D.Set SDK to Universal 10 and UWP Build Type to D3D.
  • Проверьте проекты C# для Unity.Check Unity C# Projects.
  • Щелкните Построить.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.
  • Дважды щелкните Планетариум. sln , чтобы загрузить проект в 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 выберите Window > Profiler.In Unity, select Window > Profiler.
  • Щелкните Добавить профилировщик > GPU.Click Add Profiler > GPU.
  • Щелкните активный профилировщик > .Click Active Profiler > .
  • Введите IP-адрес HoloLens.Enter the IP address of your HoloLens.
  • Нажмите кнопку Соединить.Click Connect.
  • Обратите внимание на число миллисекунд, необходимое для отображения кадра графическим процессором.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. Он будет находиться в папке Холотулкит\спатиалмаппинг проекта Assembly-CSharp (универсальные приложения для Windows).You will find it in the HoloToolkit\SpatialMapping folder of the Assembly-CSharp (Universal Windows) project.
  • Найдите функцию " спящий () " и добавьте следующую строку кода: трианглесперкубикметер = 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.

Сохранение и загрузка в UnitySave and load in Unity

Наконец, давайте экономим сетку комнаты и загружая ее в Unity.Finally, let's save our room mesh and load it into Unity.

  • Вернитесь в Visual Studio и удалите строку трианглесперкубикметер , добавленную в функцию спящего режима () во время предыдущего раздела.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. Теперь мы должны работать с 500 треугольниками на кубический метр.We should now be running with 500 triangles per cubic meter.
  • Откройте браузер и введите свой IP-адрес HoloLens, чтобы перейти на портал устройств Windows.Open a browser and enter in your HoloLens IPAddress to navigate to the Windows Device Portal.
  • Выберите параметр объемное представление на панели слева.Select the 3D View option in the left panel.
  • В разделе реконструкция поверхности нажмите кнопку Обновить .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.
  • Откройте папку downloads , чтобы найти сохраненную модель комнаты срмеш. obj.Open your Downloads folder to find the saved room model SRMesh.obj.
  • Скопируйте срмеш. obj в папку Assets проекта Unity.Copy SRMesh.obj to the Assets folder of your Unity project.
  • В Unity выберите объект спатиалмаппинг на панели Иерархия .In Unity, select the SpatialMapping object in the Hierarchy panel.
  • Нахождение компонента наблюдателя поверхности объектов (script) .Locate the Object Surface Observer (Script) component.
  • Щелкните окружность справа от свойства модель комнаты .Click the circle to the right of the Room Model property.
  • Найдите и выберите объект срмеш , а затем закройте окно.Find and select the SRMesh object and then close the window.
  • Убедитесь, что свойство " модель комнаты " на панели инспектора теперь имеет значение срмеш.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.
  • Компонент Спатиалмаппинг загрузит сетки из сохраненной модели комнаты, чтобы их можно было использовать в 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 выберите объект спатиалмаппинг .In Unity's Hierarchy panel, select the SpatialMapping object.
  • На панели инспектора найдите компонент Диспетчер пространственных сопоставлений (script) .In the Inspector panel, find the Spatial Mapping Manager (Script) component.
  • Щелкните окружность справа от свойства " материал поверхности ".Click the circle to the right of the Surface Material property.
  • Найдите и выберите материал блуелинесонваллс и закройте окно.Find and select the BlueLinesOnWalls material and close the window.
  • В папке " шейдеры " панели проекта дважды щелкните блуелинесонваллс , чтобы открыть шейдер в 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.
  • На панели проект найдите папку материалы и выберите материал блуелинесонваллс .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.
    • На панели инспектора измените значение линескале , чтобы линии были более толстыми или более тонкими.In the Inspector panel, adjust the LineScale value to make the lines appear thicker or thinner.
    • На панели инспектора настройте значение линесперметер , чтобы изменить количество линий, отображаемых на каждой стене.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 в папке голограмм найдите объект спатиалпроцессинг .In Unity's Project panel, Holograms folder, find the SpatialProcessing object.
  • Перетащите & объект спатиалпроцессинг на панель Иерархия .Drag & drop the SpatialProcessing object into the Hierarchy panel.

Спатиалпроцессинг 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. Этот prefab также включает 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.
  • На панели Иерархия выберите объект спатиалпроцессинг .In the Hierarchy panel, select the SpatialProcessing object.
  • На панели инспектора найдите компонент диспетчер пространства воспроизведения (script) .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. Используйте Сурфацемешестопланес для создания более простого представления мира в виде плоскостей (стен, пол, цеилингс и т. д.).Use SurfaceMeshesToPlanes to create a simpler representation of the world as planes (walls, floors, ceilings, etc).
    2. Используйте Ремовесурфацевертицес, чтобы удалить треугольники Surface, которые попадают в границы плоскости.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.
  • В стены рядом с камерой должны появиться два плаката.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 выберите объект спатиалпроцессинг .In Unity's Hierarchy panel, select the SpatialProcessing object.
  • На панели инспектора найдите элемент " сетки поверхности" для компонента "плоскости (скрипт)" .In the Inspector panel, find the Surface Meshes To Planes (Script) component.
  • Измените значение свойства Draw плоскости на Nothing , чтобы снять выделение.Change the Draw Planes property to Nothing to clear the selection.
  • Измените значение свойства Draw плоскости на настенный, чтобы отображались только стены.Change the Draw Planes property to Wall, so that only wall planes will be rendered.
  • На панели проект в папке скрипты дважды щелкните 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. Определить, помещается ли голограмма на поверхность, райкастинг от центра и четырех углов ограничивающего Куба.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-афиш на стене, чтобы переместить его в новое место.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

  • Определить, перекрыто ли голограмма в сетке пространственных сопоставлений.Determine if a hologram is occluded by the spatial mapping mesh.
  • Применение различных методов перекрытия для достижения привлекательного воздействия.Apply different occlusion techniques to achieve a fun effect.

ИнструкцииInstructions

Во первых, мы собираемся разрешить сетке пространственных сопоставлений окклуде другие голограммы без окклудинг реального мира:First, we are going to allow the spatial mapping mesh to occlude other holograms without occluding the real world:

  • На панели Иерархия выберите объект спатиалпроцессинг .In the Hierarchy panel, select the SpatialProcessing object.
  • На панели инспектора найдите компонент диспетчер пространства воспроизведения (script) .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.

Далее мы добавим к земле особое поведение, чтобы оно выглядело синим цветом, когда он станет перекрыто другой голограммой (например, солнце) или сеткой пространственного сопоставления: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).
  • В раскрывающемся списке шейдер замените шейдер на Custom > окклусионрим.In the Shader drop-down, change the shader to Custom > OcclusionRim. Это приведет к отображению синего выделения вокруг земли всякий раз, когда он перекрыто другим объектом.This will render a blue highlight around Earth whenever it is occluded by another object.

Наконец, мы будем включать результат концепции x-ray для планет в нашей солнечной системе.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. Определите, перекрыто ли Планета на уровне Спатиалмаппинг (сеток и плоскостей комнаты).Determine if a planet is occluded by the SpatialMapping layer (room meshes and planes).
  2. Отображение каркасного представления планеты, когда она перекрыто слоем Спатиалмаппинг.Show the wireframe representation of a planet whenever it is occluded by the SpatialMapping layer.
  3. Скрыть каркасное представление планеты, если оно не заблокировано слоем Спатиалмаппинг.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-ray и видна их каркасы.You now have x-ray vision and can see their wireframe skeletons!

КонецThe End

Поздравляем!Congratulations! Теперь вы завершили выполнение пространственного пространственного 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.
  • Вы столкнулись с различными методами перекрытия и использовали преимущества концепции x-ray!You experienced different occlusion techniques and harnessed the power of x-ray vision!