Spaziale MR 230: Mapping spazialeMR Spatial 230: Spatial mapping

Nota

Le esercitazioni di Mixed Reality Academy sono state progettate in base a HoloLens (prima generazione) e ai visori VR immersive di realtà mista.The Mixed Reality Academy tutorials were designed with HoloLens (1st gen) and Mixed Reality Immersive Headsets in mind. Pertanto, riteniamo importante lasciarle a disposizione degli sviluppatori a cui serve ancora materiale sussidiario per lo sviluppo di questi dispositivi.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. Queste esercitazioni non verranno aggiornate con i set di strumenti o le interazioni più recenti usati per HoloLens 2.These tutorials will not be updated with the latest toolsets or interactions being used for HoloLens 2. Rimarranno invariate per consentire di continuare a lavorare sui dispositivi supportati.They will be maintained to continue working on the supported devices. Per HoloLens 2 è stata pubblicata una nuova serie di esercitazioni.A new series of tutorials has been posted for HoloLens 2.

Il mapping spaziale combina il mondo reale e il mondo virtuale insegnando gli ologrammi sull'ambiente.Spatial mapping combines the real world and virtual world together by teaching holograms about the environment. In MR Spatial 230 (Project Planetarium) si apprenderà come:In MR Spatial 230 (Project Planetarium) we'll learn how to:

  • Eseguire la scansione dell'ambiente e trasferire i dati dal HoloLens al computer di sviluppo.Scan the environment and transfer data from the HoloLens to your development machine.
  • Esplora gli shader e Scopri come usarli per visualizzare lo spazio.Explore shaders and learn how to use them for visualizing your space.
  • Suddividere il mesh della stanza in semplici piani usando l'elaborazione mesh.Break down the room mesh into simple planes using mesh processing.
  • Superare le tecniche di posizionamento apprese in Mr nozioni di base 101e fornire commenti e suggerimenti sulla posizione in cui un ologramma può essere posizionato nell'ambiente.Go beyond the placement techniques we learned in MR Basics 101, and provide feedback about where a hologram can be placed in the environment.
  • Esplorare gli effetti di occlusione, pertanto quando l'ologramma è dietro un oggetto reale, è ancora possibile visualizzarlo con la visione x-ray.Explore occlusion effects, so when your hologram is behind a real-world object, you can still see it with x-ray vision!

Supporto di dispositiviDevice support

CorsoCourse HoloLensHoloLens Visori VR immersiveImmersive headsets
Spaziale MR 230: Mapping spazialeMR Spatial 230: Spatial mapping ✔️✔️

Prima di iniziareBefore you start

PrerequisitiPrerequisites

File di progettoProject files

  • Scaricare i file richiesti dal progetto.Download the files required by the project. Richiede Unity 2017,2 o versione successiva.Requires Unity 2017.2 or later.
    • Se è ancora necessario il supporto per Unity 5,6, usare questa versione.If you still need Unity 5.6 support, please use this release.
    • Se è ancora necessario il supporto per Unity 5,5, usare questa versione.If you still need Unity 5.5 support, please use this release.
    • Se è ancora necessario il supporto per Unity 5,4, usare questa versione.If you still need Unity 5.4 support, please use this release.
  • Annullare l'archiviazione dei file sul desktop o in un'altra posizione facile da raggiungere.Un-archive the files to your desktop or other easy to reach location.

Nota

Se si vuole esaminare il codice sorgente prima del download, è disponibile in GitHub.If you want to look through the source code before downloading, it's available on GitHub.

NoteNotes

  • "Enable Just My Code" in Visual Studio deve essere disabilitato (deselezionato) in strumenti > opzioni > il debug per raggiungere i punti di interruzione nel codice."Enable Just My Code" in Visual Studio needs to be disabled (unchecked) under Tools > Options > Debugging in order to hit breakpoints in your code.

Configurazione di UnityUnity setup

  • Avviare Unity.Start Unity.
  • Selezionare nuovo per creare un nuovo progetto.Select New to create a new project.
  • Denominare il progetto Planetarium.Name the project Planetarium.
  • Verificare che sia selezionata l'impostazione 3D .Verify that the 3D setting is selected.
  • Fare clic su Crea progetto.Click Create Project.
  • Una volta avviato Unity, passare a Edit > Project Settings > Player.Once Unity launches, go to Edit > Project Settings > Player.
  • Nel pannello Inspector trovare e selezionare l'icona di Windows Store verde.In the Inspector panel, find and select the green Windows Store icon.
  • Espandere altre impostazioni.Expand Other Settings.
  • Nella sezione rendering , controllare l'opzione Virtual Reality supported .In the Rendering section, check the Virtual Reality Supported option.
  • Verificare che Windows olografico sia visualizzato nell'elenco degli SDK di realtà virtuale.Verify that Windows Holographic appears in the list of Virtual Reality SDKs. In caso contrario, selezionare il + pulsante nella parte inferiore dell'elenco e scegliere Windows olografico.If not, select the + button at the bottom of the list and choose Windows Holographic.
  • Espandere impostazioni di pubblicazione.Expand Publishing Settings.
  • Nella sezione funzionalità selezionare le impostazioni seguenti:In the Capabilities section, check the following settings:
    • InternetClientServerInternetClientServer
    • PrivateNetworkClientServerPrivateNetworkClientServer
    • MicrofonoMicrophone
    • SpatialPerceptionSpatialPerception
  • Passare a modifica > impostazioni progetto > qualitàGo to Edit > Project Settings > Quality
  • Nel pannello Inspector , sotto l'icona di Windows Store, selezionare la freccia a discesa nera sotto la riga ' default ' e impostare l'impostazione predefinita su molto bassa.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.
  • Passare a asset > importa pacchetto > pacchetto personalizzato.Go to Assets > Import Package > Custom Package.
  • Passare alla cartella . ..\holographicacademy-holograms-230-SpatialMapping\Starting .Navigate to the ...\HolographicAcademy-Holograms-230-SpatialMapping\Starting folder.
  • Fare clic su Planetarium. file unitypackage Tools.Click on Planetarium.unitypackage.
  • Fare clic su Apri.Click Open.
  • Verrà visualizzata una finestra Importa pacchetto Unity , fare clic sul pulsante Importa .An Import Unity Package window should appear, click on the Import button.
  • Attendere che Unity importi tutti gli asset necessari per completare il progetto.Wait for Unity to import all of the assets that we will need to complete this project.
  • Nel pannello gerarchia eliminare la fotocamera principale.In the Hierarchy panel, delete the Main Camera.
  • Nel pannello del progetto , HoloToolkit-SpatialMapping-230\Utilities\Prefabs Folder, trovare l'oggetto Main camera .In the Project panel, HoloToolkit-SpatialMapping-230\Utilities\Prefabs folder, find the Main Camera object.
  • Trascinare e rilasciare il prefabbricato della fotocamera principale nel pannello gerarchia .Drag and drop the Main Camera prefab into the Hierarchy panel.
  • Nel pannello gerarchia eliminare l'oggetto direzionale Light .In the Hierarchy panel, delete the Directional Light object.
  • Nel pannello progetto , nella cartella ologrammi individuare l'oggetto cursore .In the Project panel, Holograms folder, locate the Cursor object.
  • Trascinare & rilasciare il prefabbricato del cursore nella gerarchia.Drag & drop the Cursor prefab into the Hierarchy.
  • Nel pannello gerarchia selezionare l'oggetto cursore .In the Hierarchy panel, select the Cursor object.
  • Nel pannello di controllo fare clic sull'elenco a discesa livello e selezionare modifica livelli....In the Inspector panel, click the Layer drop-down and select Edit Layers....
  • Nome utente livello 31 come "SpatialMapping".Name User Layer 31 as "SpatialMapping".
  • Salva la nuova scena: File > Salva scena con nome...Save the new scene: File > Save Scene As...
  • Fare clic su nuova cartella e denominare la cartella Scenes.Click New Folder and name the folder Scenes.
  • Denominare il file "Planetarium" e salvarlo nella cartella Scenes .Name the file "Planetarium" and save it in the Scenes folder.

Capitolo 1-analisiChapter 1 - Scanning

ObiettiviObjectives

  • Informazioni su SurfaceObserver e sul modo in cui le impostazioni incidono sull'esperienza e sulle prestazioni.Learn about the SurfaceObserver and how its settings impact experience and performance.
  • Consente di creare un'esperienza di analisi delle chat per raccogliere le maglie della stanza.Create a room scanning experience to collect the meshes of your room.

IstruzioniInstructions

  • Nella cartella HoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs del pannello del progetto individuare il prefabbricato SpatialMapping .In the Project panel HoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs folder, find the SpatialMapping prefab.
  • Trascinare & rilasciare la prefabbricazione SpatialMapping nel pannello gerarchia .Drag & drop the SpatialMapping prefab into the Hierarchy panel.

Compilazione e distribuzione (parte 1)Build and Deploy (part 1)

  • In Unity selezionare File > impostazioni di compilazione.In Unity, select File > Build Settings.
  • Fare clic su Aggiungi scene aperte per aggiungere la scena planetaria alla compilazione.Click Add Open Scenes to add the Planetarium scene to the build.
  • Selezionare piattaforma UWP (Universal Windows Platform) nell'elenco piattaforma e fare clic su Cambia piattaforma.Select Universal Windows Platform in the Platform list and click Switch Platform.
  • Impostare SDK su Universal 10 e tipo di compilazione UWP su D3D.Set SDK to Universal 10 and UWP Build Type to D3D.
  • Controllare i progetti C# di Unity.Check Unity C# Projects.
  • Fare clic su Compila.Click Build.
  • Creare una nuova cartella denominata "app".Create a New Folder named "App".
  • Fare clic sulla cartella dell' app .Single click the App folder.
  • Premere il pulsante Seleziona cartella .Press the Select Folder button.
  • Al termine della compilazione di Unity, viene visualizzata una finestra Esplora file.When Unity is done building, a File Explorer window will appear.
  • Fare doppio clic sulla cartella dell' app per aprirla.Double-click on the App folder to open it.
  • Fare doppio clic su Planetarium. sln per caricare il progetto in Visual Studio.Double-click on Planetarium.sln to load the project in Visual Studio.
  • In Visual Studio utilizzare la barra degli strumenti superiore per modificare la configurazione in Release.In Visual Studio, use the top toolbar to change the Configuration to Release.
  • Impostare la piattaforma su x86.Change the Platform to x86.
  • Fare clic sulla freccia a discesa a destra di "computer locale" e selezionare computer remoto.Click on the drop-down arrow to the right of 'Local Machine', and select Remote Machine.
  • Immettere l' indirizzo IP del dispositivo nel campo Address e modificare la modalità di autenticazione in Universal (protocollo non crittografato).Enter your device's IP address in the Address field and change Authentication Mode to Universal (Unencrypted Protocol).
  • Fare clic su debug-> avvia senza eseguire debug o premere CTRL + F5.Click Debug -> Start Without debugging or press Ctrl + F5.
  • Guardare il pannello output in Visual Studio per lo stato di compilazione e distribuzione.Watch the Output panel in Visual Studio for build and deploy status.
  • Una volta distribuita l'app, esaminare la chat room.Once your app has deployed, walk around the room. Vengono visualizzate le aree circostanti coperte da mesh wireframe nere e bianche.You will see the surrounding surfaces covered by black and white wireframe meshes.
  • Analizza l'ambiente circostante.Scan your surroundings. Assicurarsi di esaminare i muri, i soffitti e i piani.Be sure to look at walls, ceilings, and floors.

Compilazione e distribuzione (parte 2)Build and Deploy (part 2)

Si analizzerà ora il modo in cui il mapping spaziale può influire sulle prestazioni.Now let's explore how Spatial Mapping can affect performance.

  • In Unity selezionare finestra > Profiler.In Unity, select Window > Profiler.
  • Fare clic su Aggiungi Profiler > GPU.Click Add Profiler > GPU.
  • Fare clic su > Profiler attivo.Click Active Profiler > .
  • Immettere l' indirizzo IP del HoloLens.Enter the IP address of your HoloLens.
  • Fare clic su Connect (Connetti).Click Connect.
  • Osservare il numero di millisecondi impiegato dalla GPU per eseguire il rendering di un frame.Observe the number of milliseconds it takes for the GPU to render a frame.
  • Arrestare l'esecuzione dell'applicazione nel dispositivo.Stop the application from running on the device.
  • Tornare a Visual Studio e aprire SpatialMappingObserver.cs.Return to Visual Studio and open SpatialMappingObserver.cs. Si troverà nella cartella HoloToolkit\SpatialMapping del progetto Assembly-CSharp (Windows universale).You will find it in the HoloToolkit\SpatialMapping folder of the Assembly-CSharp (Universal Windows) project.
  • Trovare la funzione sveglie () e aggiungere la riga di codice seguente: TrianglesPerCubicMeter = 1200;Find the Awake() function, and add the following line of code: TrianglesPerCubicMeter = 1200;
  • Ridistribuire il progetto nel dispositivo e quindi riconnettere il profiler.Re-deploy the project to your device, and then reconnect the profiler. Osservare la modifica del numero di millisecondi per il rendering di un frame.Observe the change in the number of milliseconds to render a frame.
  • Arrestare l'esecuzione dell'applicazione nel dispositivo.Stop the application from running on the device.

Salva e carica in UnitySave and load in Unity

Infine, salvare il mesh della stanza e caricarlo in Unity.Finally, let's save our room mesh and load it into Unity.

  • Tornare a Visual Studio e rimuovere la riga TrianglesPerCubicMeter aggiunta nella funzione svegli () durante la sezione precedente.Return to Visual Studio and remove the TrianglesPerCubicMeter line that you added in the Awake() function during the previous section.
  • Ridistribuire il progetto nel dispositivo.Redeploy the project to your device. A questo punto dovrebbe essere in esecuzione con 500 triangoli per ogni contatore cubo.We should now be running with 500 triangles per cubic meter.
  • Aprire un browser e immettere in HoloLens IPAddress per passare al portale del dispositivo Windows.Open a browser and enter in your HoloLens IPAddress to navigate to the Windows Device Portal.
  • Selezionare l'opzione visualizzazione 3D nel pannello di sinistra.Select the 3D View option in the left panel.
  • In ricostruzione superficie selezionare il pulsante Aggiorna .Under Surface reconstruction select the Update button.
  • Osservare come le aree analizzate in HoloLens vengono visualizzate nella finestra di visualizzazione.Watch as the areas that you have scanned on your HoloLens appear in the display window.
  • Per salvare l'analisi della chat room, fare clic sul pulsante Salva .To save your room scan, press the Save button.
  • Aprire la cartella Downloads per trovare il modello di chat room salvato SRMesh. obj.Open your Downloads folder to find the saved room model SRMesh.obj.
  • Copiare SRMesh. obj nella cartella assets del progetto Unity.Copy SRMesh.obj to the Assets folder of your Unity project.
  • In Unity selezionare l'oggetto SpatialMapping nel pannello gerarchia .In Unity, select the SpatialMapping object in the Hierarchy panel.
  • Individuare il componente Observer Surface Observer (script) .Locate the Object Surface Observer (Script) component.
  • Fare clic sul cerchio a destra della proprietà del modello room .Click the circle to the right of the Room Model property.
  • Individuare e selezionare l'oggetto SRMesh e quindi chiudere la finestra.Find and select the SRMesh object and then close the window.
  • Verificare che la proprietà modello room nel pannello Inspector sia ora impostata su SRMesh.Verify that the Room Model property in the Inspector panel is now set to SRMesh.
  • Premere il pulsante Riproduci per attivare la modalità di anteprima di Unity.Press the Play button to enter Unity's preview mode.
  • Il componente SpatialMapping caricherà le mesh dal modello di chat room salvato per poterle usare in Unity.The SpatialMapping component will load the meshes from the saved room model so you can use them in Unity.
  • Passare alla visualizzazione scena per visualizzare tutto il modello di chat room visualizzato con wireframe shader.Switch to Scene view to see all of your room model displayed with the wireframe shader.
  • Premere di nuovo il pulsante Riproduci per uscire dalla modalità di anteprima.Press the Play button again to exit preview mode.

Nota: Alla successiva immissione della modalità di anteprima in Unity, verrà caricata la mesh locale salvata per impostazione predefinita.NOTE: The next time that you enter preview mode in Unity, it will load the saved room mesh by default.

Capitolo 2-visualizzazioneChapter 2 - Visualization

ObiettiviObjectives

  • Informazioni sulle nozioni di base degli shader.Learn the basics of shaders.
  • Visualizza l'ambiente circostante.Visualize your surroundings.

IstruzioniInstructions

  • Nel pannello gerarchia di Unity selezionare l'oggetto SpatialMapping .In Unity's Hierarchy panel, select the SpatialMapping object.
  • Nel pannello Inspector trovare il componente Spatial mapping Manager (script) .In the Inspector panel, find the Spatial Mapping Manager (Script) component.
  • Fare clic sul cerchio a destra della proprietà superficie materiale .Click the circle to the right of the Surface Material property.
  • Trovare e selezionare il materiale BlueLinesOnWalls e chiudere la finestra.Find and select the BlueLinesOnWalls material and close the window.
  • Nella cartella shader Panel del progetto fare doppio clic su BlueLinesOnWalls per aprire lo shader in Visual Studio.In the Project panel Shaders folder, double-click on BlueLinesOnWalls to open the shader in Visual Studio.
  • Si tratta di un pixel semplice (Vertex to fragment), che consente di eseguire le attività seguenti:This is a simple pixel (vertex to fragment) shader, which accomplishes the following tasks:
    1. Converte la posizione di un vertice in uno spazio globale.Converts a vertex's location to world space.
    2. Controlla la normale del vertice per determinare se un pixel è verticale.Checks the vertex's normal to determine if a pixel is vertical.
    3. Imposta il colore del pixel per il rendering.Sets the color of the pixel for rendering.

Compilazione e distribuzioneBuild and Deploy

  • Tornare a Unity e premere Play per passare alla modalità di anteprima.Return to Unity and press Play to enter preview mode.
  • Il rendering delle linee blu verrà eseguito su tutte le superfici verticali della mesh della stanza, che viene caricata automaticamente dai dati di analisi salvati.Blue lines will be rendered on all vertical surfaces of the room mesh (which automatically loaded from our saved scanning data).
  • Passare alla scheda scena per modificare la visualizzazione della chat room e vedere come viene visualizzata l'intera mesh room in Unity.Switch to the Scene tab to adjust your view of the room and see how the entire room mesh appears in Unity.
  • Nel pannello progetto individuare la cartella Materials e selezionare il materiale BlueLinesOnWalls .In the Project panel, find the Materials folder and select the BlueLinesOnWalls material.
  • Modificare alcune proprietà e vedere come vengono visualizzate le modifiche nell'editor di Unity.Modify some properties and see how the changes appear in the Unity editor.
    • Nel pannello Inspector modificare il valore LineScale in modo che le righe risultino più spesse o più sottili.In the Inspector panel, adjust the LineScale value to make the lines appear thicker or thinner.
    • Nel pannello Inspector regolare il valore LinesPerMeter per modificare il numero di righe visualizzate in ogni parete.In the Inspector panel, adjust the LinesPerMeter value to change how many lines appear on each wall.
  • Fare di nuovo clic su Riproduci per uscire dalla modalità anteprima.Click Play again to exit preview mode.
  • Eseguire la compilazione e la distribuzione in HoloLens e osservare come viene visualizzato il rendering dello shader su superfici reali.Build and deploy to the HoloLens and observe how the shader rendering appears on real surfaces.

Unity è un ottimo lavoro di anteprima dei materiali, ma è sempre una buona idea estrarre il rendering nel dispositivo.Unity does a great job of previewing materials, but it's always a good idea to check-out rendering in the device.

Capitolo 3-elaborazioneChapter 3 - Processing

ObiettiviObjectives

  • Informazioni sulle tecniche per elaborare i dati di mapping spaziali da usare nell'applicazione.Learn techniques to process spatial mapping data for use in your application.
  • Analizzare i dati del mapping spaziale per individuare i piani e rimuovere i triangoli.Analyze spatial mapping data to find planes and remove triangles.
  • Usare i piani per la posizione degli ologrammi.Use planes for hologram placement.

IstruzioniInstructions

  • Nel pannello del progetto di Unity, nella cartella ologrammi , trovare l'oggetto SpatialProcessing .In Unity's Project panel, Holograms folder, find the SpatialProcessing object.
  • Trascinare & rilasciare l'oggetto SpatialProcessing nel pannello gerarchia .Drag & drop the SpatialProcessing object into the Hierarchy panel.

Il preprocessore SpatialProcessing include componenti per l'elaborazione dei dati di mapping spaziali.The SpatialProcessing prefab includes components for processing the spatial mapping data. SurfaceMeshesToPlanes.cs troverà e genererà i piani in base ai dati di mapping spaziali.SurfaceMeshesToPlanes.cs will find and generate planes based on the spatial mapping data. Microsoft utilizzerà i piani nell'applicazione per rappresentare i muri, i piani e i tetti.We will use planes in our application to represent walls, floors and ceilings. Questa prefabbricata include anche RemoveSurfaceVertices.cs che possono rimuovere i vertici dalla mesh di mapping spaziale.This prefab also includes RemoveSurfaceVertices.cs which can remove vertices from the spatial mapping mesh. Questo può essere usato per creare buchi nella mesh o per rimuovere i triangoli superflui che non sono più necessari, perché è invece possibile usare i piani.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).

  • Nel pannello del progetto di Unity, cartella olografici , trovare l'oggetto Spacecollection .In Unity's Project panel, Holograms folder, find the SpaceCollection object.
  • Trascinare e rilasciare l'oggetto spaziatore nel pannello gerarchia .Drag and drop the SpaceCollection object into the Hierarchy panel.
  • Nel pannello gerarchia selezionare l'oggetto SpatialProcessing .In the Hierarchy panel, select the SpatialProcessing object.
  • Nel pannello Inspector trovare il componente Play Space Manager (script) .In the Inspector panel, find the Play Space Manager (Script) component.
  • Fare doppio clic su PlaySpaceManager.cs per aprirlo in Visual Studio.Double-click on PlaySpaceManager.cs to open it in Visual Studio.

PlaySpaceManager.cs contiene codice specifico dell'applicazione.PlaySpaceManager.cs contains application-specific code. Si aggiungeranno funzionalità a questo script per abilitare il comportamento seguente:We will add functionality to this script to enable the following behavior:

  1. Interrompere la raccolta dei dati di mapping spaziali dopo il superamento del limite di tempo di analisi (10 secondi).Stop collecting spatial mapping data after we exceed the scanning time limit (10 seconds).
  2. Elaborare i dati di mapping spaziale:Process the spatial mapping data:
    1. Usare SurfaceMeshesToPlanes per creare una rappresentazione più semplice del mondo come piani (muri, piani, soffitti e così via).Use SurfaceMeshesToPlanes to create a simpler representation of the world as planes (walls, floors, ceilings, etc).
    2. Usare RemoveSurfaceVertices per rimuovere i triangoli di superficie che rientrano nei limiti del piano.Use RemoveSurfaceVertices to remove surface triangles that fall within plane boundaries.
  3. Generare una raccolta di ologrammi in tutto il mondo e posizionarli sui piani di parete e di piano vicino all'utente.Generate a collection of holograms in the world and place them on wall and floor planes near the user.

Completare gli esercizi di codifica contrassegnati in PlaySpaceManager.cs o sostituire lo script con la soluzione completa riportata di seguito: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;
        }
    }
}

Compilazione e distribuzioneBuild and Deploy

  • Prima di eseguire la distribuzione in HoloLens, premere il pulsante Riproduci in Unity per attivare la modalità di riproduzione.Before deploying to the HoloLens, press the Play button in Unity to enter play mode.
  • Dopo che la mesh Room è stata caricata dal file, attendere 10 secondi prima che l'elaborazione venga avviata sulla mesh di mapping spaziale.After the room mesh is loaded from file, wait for 10 seconds before processing starts on the spatial mapping mesh.
  • Al termine dell'elaborazione, i piani verranno visualizzati per rappresentare il piano, i muri, il soffitto e così via.When processing is complete, planes will appear to represent the floor, walls, ceiling, etc.
  • Una volta trovati tutti i piani, viene visualizzato un sistema solare in una tabella di piano vicino alla fotocamera.After all of the planes have been found, you should see a solar system appear on a table of floor near the camera.
  • Verranno visualizzati due poster anche accanto alla fotocamera.Two posters should appear on walls near the camera too. Passare alla scheda scena se non è possibile visualizzarli in modalità di gioco .Switch to the Scene tab if you cannot see them in Game mode.
  • Premere di nuovo il pulsante Riproduci per uscire dalla modalità di riproduzione.Press the Play button again to exit play mode.
  • Compilare e distribuire in HoloLens, come di consueto.Build and deploy to the HoloLens, as usual.
  • Attendere il completamento dell'analisi e dell'elaborazione dei dati di mapping spaziali.Wait for scanning and processing of the spatial mapping data to complete.
  • Dopo aver visualizzato i piani, provare a trovare il sistema solare e i poster nel mondo.Once you see planes, try to find the solar system and posters in your world.

Capitolo 4-selezione hostChapter 4 - Placement

ObiettiviObjectives

  • Determinare se un ologramma si adatta a una superficie.Determine if a hologram will fit on a surface.
  • Fornire commenti e suggerimenti all'utente quando un ologramma può o non può adattarsi a una superficie.Provide feedback to the user when a hologram can/cannot fit on a surface.

IstruzioniInstructions

  • Nel pannello gerarchia di Unity selezionare l'oggetto SpatialProcessing .In Unity's Hierarchy panel, select the SpatialProcessing object.
  • Nel pannello Inspector trovare il componente Surface Meshs to Planes (script) .In the Inspector panel, find the Surface Meshes To Planes (Script) component.
  • Per cancellare la selezione, modificare la proprietà piani di estrazione in Nessun elemento.Change the Draw Planes property to Nothing to clear the selection.
  • Modificare la proprietà dei piani di disegna su Wall, in modo che venga eseguito il rendering solo dei piani di muro.Change the Draw Planes property to Wall, so that only wall planes will be rendered.
  • Nel pannello progetto , cartella script , fare doppio clic su Placeable.cs per aprirlo in Visual Studio.In the Project panel, Scripts folder, double-click on Placeable.cs to open it in Visual Studio.

Lo script posizionabile è già collegato ai manifesti e alla casella di proiezione creati al termine della ricerca del piano.The Placeable script is already attached to the posters and projection box that are created after plane finding completes. È sufficiente rimuovere il commento dal codice e questo script consentirà di ottenere i risultati seguenti:All we need to do is uncomment some code, and this script will achieve the following:

  1. Determinare se un ologramma si adatta a una superficie Raycasting dal centro e da quattro angoli del cubo di delimitazione.Determine if a hologram will fit on a surface by raycasting from the center and four corners of the bounding cube.
  2. Controllare la normale della superficie per determinare se è sufficientemente uniforme per consentire l'ologramma in cui si trova lo scaricamento.Check the surface normal to determine if it is smooth enough for the hologram to sit flush on.
  3. Eseguire il rendering di un cubo di delimitazione intorno all'ologramma per visualizzarne le dimensioni effettive durante la posizione.Render a bounding cube around the hologram to show its actual size while being placed.
  4. Consente di eseguire il cast di un'ombreggiatura sotto/dietro l'ologramma per mostrare dove verrà posizionata sul pavimento o sul muro.Cast a shadow under/behind the hologram to show where it will be placed on the floor/wall.
  5. Eseguire il rendering dell'ombreggiatura in rosso, se l'ologramma non può essere inserito sulla superficie o verde, se possibile.Render the shadow as red, if the hologram cannot be placed on the surface, or green, if it can.
  6. Riorientare l'ologramma per allinearlo al tipo di superficie (verticale o orizzontale) con affinità.Re-orient the hologram to align with the surface type (vertical or horizontal) that it has affinity to.
  7. Posizionare in modo uniforme l'ologramma sulla superficie selezionata per evitare il comportamento di salto o blocco.Smoothly place the hologram on the selected surface to avoid jumping or snapping behavior.

Rimuovere il commento dal codice nell'esercizio di codifica riportato di seguito oppure usare questa soluzione completata in 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;
    }
}

Compilazione e distribuzioneBuild and Deploy

  • Come in precedenza, compilare il progetto e distribuirlo in HoloLens.As before, build the project and deploy to the HoloLens.
  • Attendere il completamento dell'analisi e dell'elaborazione dei dati di mapping spaziali.Wait for scanning and processing of the spatial mapping data to complete.
  • Quando viene visualizzato il sistema solare, osservare la casella di proiezione seguente ed eseguire un movimento di selezione per spostarlo.When you see the solar system, gaze at the projection box below and perform a select gesture to move it around. Mentre la casella di proiezione è selezionata, un cubo di delimitazione sarà visibile intorno alla casella di proiezione.While the projection box is selected, a bounding cube will be visible around the projection box.
  • Sposta la testa per ammirare una posizione diversa nella stanza.Move you head to gaze at a different location in the room. La finestra di proiezione dovrebbe seguire lo sguardo.The projection box should follow your gaze. Quando l'ombreggiatura sotto la casella di proiezione è rossa, non è possibile posizionare l'ologramma su tale superficie.When the shadow below the projection box turns red, you cannot place the hologram on that surface. Quando l'ombreggiatura sotto la casella di proiezione risulta verde, è possibile posizionare l'ologramma eseguendo un altro movimento di selezione.When the shadow below the projection box turns green, you can place the hologram by performing another select gesture.
  • Trovare e selezionare uno dei poster olografici su un muro per spostarlo in una nuova posizione.Find and select one of the holographic posters on a wall to move it to a new location. Si noti che non è possibile posizionare il poster sul pavimento o sul soffitto e che rimane correttamente orientato a ogni muro mentre ci si sposta.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.

Capitolo 5-occlusioneChapter 5 - Occlusion

ObiettiviObjectives

  • Determinare se un ologramma è nascosto dalla mesh di mapping spaziale.Determine if a hologram is occluded by the spatial mapping mesh.
  • Applicare diverse tecniche di occlusione per ottenere un effetto divertente.Apply different occlusion techniques to achieve a fun effect.

IstruzioniInstructions

In primo luogo, si consentirà alla rete di mapping spaziale di occludere altri ologrammi senza occlusione il mondo reale:First, we are going to allow the spatial mapping mesh to occlude other holograms without occluding the real world:

  • Nel pannello gerarchia selezionare l'oggetto SpatialProcessing .In the Hierarchy panel, select the SpatialProcessing object.
  • Nel pannello Inspector trovare il componente Play Space Manager (script) .In the Inspector panel, find the Play Space Manager (Script) component.
  • Fare clic sul cerchio a destra della proprietà Material secondaria .Click the circle to the right of the Secondary Material property.
  • Trovare e selezionare il materiale di occlusione e chiudere la finestra.Find and select the Occlusion material and close the window.

Successivamente, si aggiungerà un comportamento speciale a Earth, in modo che abbia un'evidenziazione blu ogni volta che diventa bloccato da un altro ologramma (ad esempio il sole) o dalla mesh di mapping spaziale: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:

  • Nel pannello progetto , nella cartella ologrammi , espandere l'oggetto Solarsystem .In the Project panel, in the Holograms folder, expand the SolarSystem object.
  • Fare clic su Earth.Click on Earth.
  • Nel pannello Inspector trovare il materiale della terra (componente inferiore).In the Inspector panel, find the Earth's material (bottom component).
  • Nell' elenco a discesa shader impostare lo shader su Custom > OcclusionRim.In the Shader drop-down, change the shader to Custom > OcclusionRim. Verrà eseguito il rendering di un'evidenziazione blu attorno alla terra ogni volta che viene nascosto da un altro oggetto.This will render a blue highlight around Earth whenever it is occluded by another object.

Infine, si Abilita un effetto di visione x-ray per i pianeti nel nostro sistema solare.Finally, we are going to enable an x-ray vision effect for planets in our solar system. È necessario modificare PlanetOcclusion.cs (presente nella cartella Scripts\SolarSystem) per ottenere i risultati seguenti:We will need to edit PlanetOcclusion.cs (found in the Scripts\SolarSystem folder) in order to achieve the following:

  1. Determinare se un pianeta è bloccato dal livello SpatialMapping (maglie e piani della stanza).Determine if a planet is occluded by the SpatialMapping layer (room meshes and planes).
  2. Mostra la rappresentazione wireframe di un pianeta ogni volta che viene bloccato dal livello SpatialMapping.Show the wireframe representation of a planet whenever it is occluded by the SpatialMapping layer.
  3. Nascondere la rappresentazione wireframe di un pianeta quando non è bloccata dal livello SpatialMapping.Hide the wireframe representation of a planet when it is not blocked by the SpatialMapping layer.

Seguire l'esercizio di codifica in PlanetOcclusion.cs o usare la soluzione seguente: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);
            }
        }
    }
}

Compilazione e distribuzioneBuild and Deploy

  • Compilare e distribuire l'applicazione in HoloLens, come di consueto.Build and deploy the application to HoloLens, as usual.
  • Attendere che l'analisi e l'elaborazione dei dati del mapping spaziale siano completate (le linee blu verranno visualizzate sui muri).Wait for scanning and processing of the spatial mapping data to be complete (you should see blue lines appear on walls).
  • Trovare e selezionare la casella di proiezione del sistema solare, quindi impostare la casella accanto a una parete o dietro un contatore.Find and select the solar system's projection box and then set the box next to a wall or behind a counter.
  • È possibile visualizzare l'occlusione di base nascondendo dietro le superfici al peer nel poster o nella casella di proiezione.You can view basic occlusion by hiding behind surfaces to peer at the poster or projection box.
  • Si osservi il terreno. dovrebbe essere presente un effetto evidenziato blu ogni volta che viene posizionato dietro un altro ologramma o superficie.Look for the Earth, there should be a blue highlight effect whenever it goes behind another hologram or surface.
  • Osservare come i pianeti si spostano dietro la parete o altre superfici della stanza.Watch as the planets move behind the wall or other surfaces in the room. A questo punto è disponibile la visione x-ray e possono essere visualizzati gli scheletri wireframe.You now have x-ray vision and can see their wireframe skeletons!

La fineThe End

La procedura è stata completata.Congratulations! A questo punto è stato completato il mapping spaziale 230: mapping spaziale.You have now completed MR Spatial 230: Spatial mapping.

  • Si sa come analizzare l'ambiente e caricare i dati di mapping spaziale in Unity.You know how to scan your environment and load spatial mapping data to Unity.
  • Si conoscono le nozioni di base degli shader e il modo in cui è possibile usare i materiali per visualizzare nuovamente il mondo.You understand the basics of shaders and how materials can be used to re-visualize the world.
  • Sono state apprese le nuove tecniche di elaborazione per individuare i piani e rimuovere i triangoli da una mesh.You learned of new processing techniques for finding planes and removing triangles from a mesh.
  • È possibile spostare e posizionare gli ologrammi su superfici che hanno senso.You were able to move and place holograms on surfaces that made sense.
  • Sono state rilevate diverse tecniche di occlusione e sono state sfruttate le potenzialità della visione x-ray.You experienced different occlusion techniques and harnessed the power of x-ray vision!