Réalité mixte - Fonctionnalités spatiales - Cours 230 : Mappage spatialMR Spatial 230: Spatial mapping

Notes

Les tutoriels Mixed Reality Academy ont été conçus pour les appareils HoloLens (1re génération) et les casques immersifs de réalité mixte.The Mixed Reality Academy tutorials were designed with HoloLens (1st gen) and Mixed Reality Immersive Headsets in mind. Nous estimons qu’il est important de laisser ces tutoriels à la disposition des développeurs qui recherchent encore des conseils pour développer des applications sur ces appareils.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. Notez que ces tutoriels ne sont pas mis à jour avec les derniers ensembles d’outils ou interactions utilisés pour HoloLens 2.These tutorials will not be updated with the latest toolsets or interactions being used for HoloLens 2. Ils sont fournis dans le but de fonctionner sur les appareils pris en charge.They will be maintained to continue working on the supported devices. Une nouvelle série de tutoriels a été publiée pour HoloLens 2.A new series of tutorials has been posted for HoloLens 2.

Le mappage spatial combine le monde réel et le monde virtuel ensemble en apprenant des hologrammes sur l’environnement.Spatial mapping combines the real world and virtual world together by teaching holograms about the environment. Dans l’spatial spatial 230 (Project Planetarium), nous allons apprendre à :In MR Spatial 230 (Project Planetarium) we'll learn how to:

  • Analyser l’environnement et transférer des données de HoloLens vers votre ordinateur de développement.Scan the environment and transfer data from the HoloLens to your development machine.
  • Explorez les nuanceurs et apprenez à les utiliser pour visualiser votre espace.Explore shaders and learn how to use them for visualizing your space.
  • Décomposez le maillage de la pièce en plans simples à l’aide du traitement de maillage.Break down the room mesh into simple planes using mesh processing.
  • Allez au-delà des techniques de placement que nous avons appris dans les notions de base de m. 101et fournissez des commentaires sur l’endroit où un hologramme peut être placé dans l’environnement.Go beyond the placement techniques we learned in MR Basics 101, and provide feedback about where a hologram can be placed in the environment.
  • Explorez les effets d’occlusion. par conséquent, lorsque votre hologramme se trouve derrière un objet réel, vous pouvez toujours le voir avec la vision x-ray !Explore occlusion effects, so when your hologram is behind a real-world object, you can still see it with x-ray vision!

Prise en charge des appareilsDevice support

CoursCourse HoloLensHoloLens Casques immersifsImmersive headsets
Réalité mixte - Fonctionnalités spatiales - Cours 230 : Mappage spatialMR Spatial 230: Spatial mapping ✔️✔️

Avant de commencerBefore you start

PrérequisPrerequisites

Fichiers projetProject files

  • Téléchargez les fichiers requis par le projet.Download the files required by the project. Requiert Unity 2017,2 ou une version ultérieure.Requires Unity 2017.2 or later.
    • Si vous avez encore besoin de la prise en charge d’Unity 5,6, utilisez cette version.If you still need Unity 5.6 support, please use this release.
    • Si vous avez encore besoin de la prise en charge d’Unity 5,5, utilisez cette version.If you still need Unity 5.5 support, please use this release.
    • Si vous avez encore besoin de la prise en charge d’Unity 5,4, utilisez cette version.If you still need Unity 5.4 support, please use this release.
  • Désarchivez les fichiers sur votre bureau ou un autre emplacement facile à atteindre.Un-archive the files to your desktop or other easy to reach location.

Notes

Si vous souhaitez examiner le code source avant le téléchargement, il est disponible sur GitHub.If you want to look through the source code before downloading, it's available on GitHub.

NotesNotes

  • Dans Visual Studio, l’option « Activer Uniquement mon code » doit être désactivée (décochée) sous outils > options > débogage pour atteindre les points d’arrêt dans votre code."Enable Just My Code" in Visual Studio needs to be disabled (unchecked) under Tools > Options > Debugging in order to hit breakpoints in your code.

Configuration de UnityUnity setup

  • Démarrez Unity.Start Unity.
  • Sélectionnez nouveau pour créer un nouveau projet.Select New to create a new project.
  • Nommez le projet Planetarium.Name the project Planetarium.
  • Vérifiez que le paramètre 3D est sélectionné.Verify that the 3D setting is selected.
  • Cliquez sur créer un projet.Click Create Project.
  • Une fois Unity lancé, accédez à modifier > paramètres du projet > Player.Once Unity launches, go to Edit > Project Settings > Player.
  • Dans le volet de l' inspecteur , recherchez et sélectionnez l’icône du Windows Store vert.In the Inspector panel, find and select the green Windows Store icon.
  • Développez autres paramètres.Expand Other Settings.
  • Dans la section rendu , activez la case à cocher prise en charge de la réalité virtuelle .In the Rendering section, check the Virtual Reality Supported option.
  • Vérifiez que Windows holographique apparaît dans la liste des kits de développement logiciel (SDK) de réalité virtuelle.Verify that Windows Holographic appears in the list of Virtual Reality SDKs. Si ce n’est pas le cas, sélectionnez le + bouton en bas de la liste et choisissez Windows holographique.If not, select the + button at the bottom of the list and choose Windows Holographic.
  • Développez paramètres de publication.Expand Publishing Settings.
  • Dans la section fonctionnalités , vérifiez les paramètres suivants :In the Capabilities section, check the following settings:
    • InternetClientServerInternetClientServer
    • PrivateNetworkClientServerPrivateNetworkClientServer
    • MicrophoneMicrophone
    • SpatialPerceptionSpatialPerception
  • Accédez à modifier > paramètres du projet > qualitéGo to Edit > Project Settings > Quality
  • Dans le volet de l' inspecteur , sous l’icône Windows Store, sélectionnez la flèche déroulante noire sous la ligne « default » et définissez le paramètre par défaut sur très faible.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.
  • Accédez à ressources > importer un package > package personnalisé.Go to Assets > Import Package > Custom Package.
  • Accédez au dossier . ..\holographicacademy-holograms-230-SpatialMapping\Starting .Navigate to the ...\HolographicAcademy-Holograms-230-SpatialMapping\Starting folder.
  • Cliquez sur Planetarium. pour Unity.Click on Planetarium.unitypackage.
  • Cliquez sur Ouvrir.Click Open.
  • Une fenêtre Importer un package Unity doit s’afficher. cliquez sur le bouton Importer .An Import Unity Package window should appear, click on the Import button.
  • Attendez que Unity importe toutes les ressources dont nous aurons besoin pour terminer ce projet.Wait for Unity to import all of the assets that we will need to complete this project.
  • Dans le panneau hiérarchie , supprimez l' appareil photo principal.In the Hierarchy panel, delete the Main Camera.
  • Dans le panneau projet , dans le dossier HoloToolkit-SpatialMapping-230\Utilities\Prefabs , recherchez l’objet Camera principal .In the Project panel, HoloToolkit-SpatialMapping-230\Utilities\Prefabs folder, find the Main Camera object.
  • Glissez-déplacez le Prefab d' appareil photo principal dans le panneau de hiérarchie .Drag and drop the Main Camera prefab into the Hierarchy panel.
  • Dans le panneau hiérarchie , supprimez l’objet de lumière directionnelle .In the Hierarchy panel, delete the Directional Light object.
  • Dans le panneau projet , dans le dossier hologrammes , localisez l’objet curseur .In the Project panel, Holograms folder, locate the Cursor object.
  • Faites glisser & déposez le curseur Prefab dans la hiérarchie.Drag & drop the Cursor prefab into the Hierarchy.
  • Dans le volet hiérarchie , sélectionnez l’objet curseur .In the Hierarchy panel, select the Cursor object.
  • Dans le panneau inspecteur , cliquez sur la liste déroulante couche , puis sélectionnez modifier les couches....In the Inspector panel, click the Layer drop-down and select Edit Layers....
  • Nommez User Layer 31 en «SpatialMapping».Name User Layer 31 as "SpatialMapping".
  • Enregistrez la nouvelle scène : fichier > enregistrer la scène sous...Save the new scene: File > Save Scene As...
  • Cliquez sur nouveau dossier et nommez le dossier scenes.Click New Folder and name the folder Scenes.
  • Nommez le fichier «Planetarium» et enregistrez-le dans le dossier scenes .Name the file "Planetarium" and save it in the Scenes folder.

Chapitre 1-analyseChapter 1 - Scanning

ObjectifsObjectives

  • Découvrez le SurfaceObserver et comment ses paramètres affectent l’expérience et les performances.Learn about the SurfaceObserver and how its settings impact experience and performance.
  • Créez une expérience de numérisation de salle pour collecter les mailles de votre salle.Create a room scanning experience to collect the meshes of your room.

InstructionsInstructions

  • Dans le dossier HoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs du panneau projet , recherchez le Prefab SpatialMapping .In the Project panel HoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs folder, find the SpatialMapping prefab.
  • Faites glisser & déposer le Prefab SpatialMapping dans le panneau de hiérarchie .Drag & drop the SpatialMapping prefab into the Hierarchy panel.

Création et déploiement (partie 1)Build and Deploy (part 1)

  • Dans Unity, sélectionnez fichier > paramètres de build.In Unity, select File > Build Settings.
  • Cliquez sur Ajouter des scènes ouvertes pour ajouter la scène Planetarium à la Build.Click Add Open Scenes to add the Planetarium scene to the build.
  • Sélectionnez plateforme Windows universelle dans la liste plateforme , puis cliquez sur basculer la plateforme.Select Universal Windows Platform in the Platform list and click Switch Platform.
  • Définissez le SDK sur le type de build Universal 10 et UWP sur D3D.Set SDK to Universal 10 and UWP Build Type to D3D.
  • Vérifiez les projets Unity C#.Check Unity C# Projects.
  • Cliquez sur Générer.Click Build.
  • Créez un dossier nommé « App ».Create a New Folder named "App".
  • Cliquez sur le dossier de l' application .Single click the App folder.
  • Appuyez sur le bouton Sélectionner un dossier .Press the Select Folder button.
  • Une fois la création d’Unity terminée, une fenêtre de l’Explorateur de fichiers s’affiche.When Unity is done building, a File Explorer window will appear.
  • Double-cliquez sur le dossier de l' application pour l’ouvrir.Double-click on the App folder to open it.
  • Double-cliquez sur Planetarium. sln pour charger le projet dans Visual Studio.Double-click on Planetarium.sln to load the project in Visual Studio.
  • Dans Visual Studio, utilisez la barre d’outils supérieure pour changer la configuration en Release.In Visual Studio, use the top toolbar to change the Configuration to Release.
  • Remplacez la plateforme par x86.Change the Platform to x86.
  • Cliquez sur la flèche déroulante à droite de « ordinateur local », puis sélectionnez ordinateur distant.Click on the drop-down arrow to the right of 'Local Machine', and select Remote Machine.
  • Entrez l' adresse IP de votre appareil dans le champ adresse et changez le mode d’authentification en protocole universel (protocole non chiffré).Enter your device's IP address in the Address field and change Authentication Mode to Universal (Unencrypted Protocol).
  • Cliquez sur Déboguer-> exécuter sans débogage ou appuyez sur CTRL + F5.Click Debug -> Start Without debugging or press Ctrl + F5.
  • Regardez le panneau de sortie dans Visual Studio pour l’état de génération et de déploiement.Watch the Output panel in Visual Studio for build and deploy status.
  • Une fois votre application déployée, parcourez la salle.Once your app has deployed, walk around the room. Vous verrez les surfaces environnantes couvertes par les maillages filaires noir et blanc.You will see the surrounding surfaces covered by black and white wireframe meshes.
  • Analysez votre environnement.Scan your surroundings. Veillez à examiner les murs, les limites et les étages.Be sure to look at walls, ceilings, and floors.

Génération et déploiement (partie 2)Build and Deploy (part 2)

Voyons maintenant comment le mappage spatial peut affecter les performances.Now let's explore how Spatial Mapping can affect performance.

  • Dans Unity, sélectionnez windows > Profiler.In Unity, select Window > Profiler.
  • Cliquez sur Ajouter profiler > GPU.Click Add Profiler > GPU.
  • Cliquez sur > du profileur actif.Click Active Profiler > .
  • Entrez l' adresse IP de votre HoloLens.Enter the IP address of your HoloLens.
  • Cliquez sur Connecter.Click Connect.
  • Observez le nombre de millisecondes nécessaires au rendu d’une trame par le GPU.Observe the number of milliseconds it takes for the GPU to render a frame.
  • Arrêtez l’exécution de l’application sur l’appareil.Stop the application from running on the device.
  • Revenez à Visual Studio et ouvrez SpatialMappingObserver.cs.Return to Visual Studio and open SpatialMappingObserver.cs. Vous le trouverez dans le dossier HoloToolkit\SpatialMapping du projet Assembly-CSharp (Windows universel).You will find it in the HoloToolkit\SpatialMapping folder of the Assembly-CSharp (Universal Windows) project.
  • Recherchez la fonction éveillé () , puis ajoutez la ligne de code suivante : TrianglesPerCubicMeter = 1200 ;Find the Awake() function, and add the following line of code: TrianglesPerCubicMeter = 1200;
  • Redéployez le projet sur votre appareil, puis reconnectez le profileur.Re-deploy the project to your device, and then reconnect the profiler. Observez la modification du nombre de millisecondes pour le rendu d’un frame.Observe the change in the number of milliseconds to render a frame.
  • Arrêtez l’exécution de l’application sur l’appareil.Stop the application from running on the device.

Enregistrer et charger dans UnitySave and load in Unity

Enfin, nous allons enregistrer notre maillage d’espace et le charger dans Unity.Finally, let's save our room mesh and load it into Unity.

  • Revenez à Visual Studio et supprimez la ligne TrianglesPerCubicMeter que vous avez ajoutée dans la fonction éveillé () au cours de la section précédente.Return to Visual Studio and remove the TrianglesPerCubicMeter line that you added in the Awake() function during the previous section.
  • Redéployez le projet sur votre appareil.Redeploy the project to your device. Nous devons maintenant être en cours d’exécution avec des triangles 500 par mètre cube.We should now be running with 500 triangles per cubic meter.
  • Ouvrez un navigateur et entrez dans votre adresseIP HoloLens pour accéder au portail de périphériques Windows.Open a browser and enter in your HoloLens IPAddress to navigate to the Windows Device Portal.
  • Sélectionnez l’option vue 3D dans le volet gauche.Select the 3D View option in the left panel.
  • Sous reconstruction de la surface, sélectionnez le bouton mettre à jour .Under Surface reconstruction select the Update button.
  • Regardez que les zones que vous avez analysées dans votre HoloLens s’affichent dans la fenêtre d’affichage.Watch as the areas that you have scanned on your HoloLens appear in the display window.
  • Pour enregistrer l’analyse de votre salle, appuyez sur le bouton Enregistrer .To save your room scan, press the Save button.
  • Ouvrez votre dossier téléchargements pour rechercher le modèle de salle enregistré SRMesh. obj.Open your Downloads folder to find the saved room model SRMesh.obj.
  • Copiez SRMesh. obj dans le dossier composants de votre projet Unity.Copy SRMesh.obj to the Assets folder of your Unity project.
  • Dans Unity, sélectionnez l’objet SpatialMapping dans le panneau hiérarchie .In Unity, select the SpatialMapping object in the Hierarchy panel.
  • Localisez le composant Observateur de surface d’objet (script) .Locate the Object Surface Observer (Script) component.
  • Cliquez sur le cercle situé à droite de la propriété modèle de salle .Click the circle to the right of the Room Model property.
  • Recherchez et sélectionnez l’objet SRMesh , puis fermez la fenêtre.Find and select the SRMesh object and then close the window.
  • Vérifiez que la propriété modèle de salle dans le panneau inspecteur a désormais la valeur SRMesh.Verify that the Room Model property in the Inspector panel is now set to SRMesh.
  • Appuyez sur le bouton de lecture pour entrer en mode aperçu Unity.Press the Play button to enter Unity's preview mode.
  • Le composant SpatialMapping charge les maillages à partir du modèle de salle enregistré, afin que vous puissiez les utiliser dans Unity.The SpatialMapping component will load the meshes from the saved room model so you can use them in Unity.
  • Basculez en vue scène pour voir l’ensemble de votre modèle d’espace affiché avec le nuanceur filaire.Switch to Scene view to see all of your room model displayed with the wireframe shader.
  • Appuyez de nouveau sur le bouton lecture pour quitter le mode aperçu.Press the Play button again to exit preview mode.

Remarque : La prochaine fois que vous passerez en mode aperçu dans Unity, le maillage Room enregistré sera chargé par défaut.NOTE: The next time that you enter preview mode in Unity, it will load the saved room mesh by default.

Chapitre 2-visualisationChapter 2 - Visualization

ObjectifsObjectives

  • Découvrez les principes de base des nuanceurs.Learn the basics of shaders.
  • Visualisez votre environnement.Visualize your surroundings.

InstructionsInstructions

  • Dans le panneau de la hiérarchie Unity, sélectionnez l’objet SpatialMapping .In Unity's Hierarchy panel, select the SpatialMapping object.
  • Dans le volet de l' inspecteur , recherchez le composant de Gestionnaire de mappage spatial (script) .In the Inspector panel, find the Spatial Mapping Manager (Script) component.
  • Cliquez sur le cercle situé à droite de la propriété surface .Click the circle to the right of the Surface Material property.
  • Recherchez et sélectionnez le matériel BlueLinesOnWalls et fermez la fenêtre.Find and select the BlueLinesOnWalls material and close the window.
  • Dans le dossier nuanciers du panneau projet , double-cliquez sur BlueLinesOnWalls pour ouvrir le nuanceur dans Visual Studio.In the Project panel Shaders folder, double-click on BlueLinesOnWalls to open the shader in Visual Studio.
  • Il s’agit d’un nuanceur de pixels (vertex à fragment) simple qui effectue les tâches suivantes :This is a simple pixel (vertex to fragment) shader, which accomplishes the following tasks:
    1. Convertit l’emplacement d’un vertex en espace universel.Converts a vertex's location to world space.
    2. Vérifie la normale du vertex pour déterminer si un pixel est vertical.Checks the vertex's normal to determine if a pixel is vertical.
    3. Définit la couleur du pixel pour le rendu.Sets the color of the pixel for rendering.

Génération et déploiementBuild and Deploy

  • Revenez à Unity et appuyez sur Play pour passer en mode aperçu.Return to Unity and press Play to enter preview mode.
  • Les lignes bleues sont affichées sur toutes les surfaces verticales de la maille de la salle (qui sont chargées automatiquement à partir de nos données d’analyse enregistrées).Blue lines will be rendered on all vertical surfaces of the room mesh (which automatically loaded from our saved scanning data).
  • Basculez vers l’onglet scène pour ajuster la vue de la pièce et voir comment l’intégralité du maillage de la salle apparaît dans Unity.Switch to the Scene tab to adjust your view of the room and see how the entire room mesh appears in Unity.
  • Dans le panneau projet , recherchez le dossier matériaux et sélectionnez le matériel BlueLinesOnWalls .In the Project panel, find the Materials folder and select the BlueLinesOnWalls material.
  • Modifiez certaines propriétés et observez le mode d’affichage des modifications dans l’éditeur Unity.Modify some properties and see how the changes appear in the Unity editor.
    • Dans le panneau inspecteur , ajustez la valeur LineScale pour que les lignes apparaissent plus épaisses ou plus fines.In the Inspector panel, adjust the LineScale value to make the lines appear thicker or thinner.
    • Dans le panneau inspecteur , ajustez la valeur LinesPerMeter pour modifier le nombre de lignes qui s’affichent sur chaque mur.In the Inspector panel, adjust the LinesPerMeter value to change how many lines appear on each wall.
  • Cliquez à nouveau sur lire pour quitter le mode aperçu.Click Play again to exit preview mode.
  • Générez et déployez sur HoloLens et observez la façon dont le rendu du nuanceur apparaît sur les surfaces réelles.Build and deploy to the HoloLens and observe how the shader rendering appears on real surfaces.

Unity fait un excellent travail d’aperçu des documents, mais il est toujours judicieux d’extraire le rendu dans l’appareil.Unity does a great job of previewing materials, but it's always a good idea to check-out rendering in the device.

Chapitre 3-traitementChapter 3 - Processing

ObjectifsObjectives

  • Découvrez les techniques permettant de traiter les données de mappage spatiale pour une utilisation dans votre application.Learn techniques to process spatial mapping data for use in your application.
  • Analyser les données de mappage spatiale pour rechercher des plans et supprimer des triangles.Analyze spatial mapping data to find planes and remove triangles.
  • Utilisez des plans pour le placement de l’hologramme.Use planes for hologram placement.

InstructionsInstructions

  • Dans le panneau projet d’Unity, dans le dossier hologrammes , recherchez l’objet SpatialProcessing .In Unity's Project panel, Holograms folder, find the SpatialProcessing object.
  • Faites glisser & déposez l’objet SpatialProcessing dans le panneau de hiérarchie .Drag & drop the SpatialProcessing object into the Hierarchy panel.

SpatialProcessing Prefab comprend des composants pour traiter les données de mappage spatiale.The SpatialProcessing prefab includes components for processing the spatial mapping data. SurfaceMeshesToPlanes.cs trouvera et générera des plans basés sur les données de mappage spatiale.SurfaceMeshesToPlanes.cs will find and generate planes based on the spatial mapping data. Nous utiliserons des plans dans notre application pour représenter les murs, les étages et les plafonds.We will use planes in our application to represent walls, floors and ceilings. Ce Prefab comprend également RemoveSurfaceVertices.cs qui peut supprimer des vertex du maillage de mappage spatial.This prefab also includes RemoveSurfaceVertices.cs which can remove vertices from the spatial mapping mesh. Cela peut être utilisé pour créer des trous dans la maille ou pour supprimer les triangles excédentaires qui ne sont plus nécessaires (car les plans peuvent être utilisés à la place).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).

  • Dans le panneau projet d’Unity, dans le dossier hologrammes , recherchez l’objet SpaceCollection .In Unity's Project panel, Holograms folder, find the SpaceCollection object.
  • Faites glisser et déposez l’objet SpaceCollection dans le panneau de hiérarchie .Drag and drop the SpaceCollection object into the Hierarchy panel.
  • Dans le volet hiérarchie , sélectionnez l’objet SpatialProcessing .In the Hierarchy panel, select the SpatialProcessing object.
  • Dans le volet de l' inspecteur , recherchez le composant Gestionnaire d’espace de lecture (script) .In the Inspector panel, find the Play Space Manager (Script) component.
  • Double-cliquez sur PlaySpaceManager.cs pour l’ouvrir dans Visual Studio.Double-click on PlaySpaceManager.cs to open it in Visual Studio.

PlaySpaceManager.cs contient le code spécifique à l’application.PlaySpaceManager.cs contains application-specific code. Nous ajouterons des fonctionnalités à ce script pour activer le comportement suivant :We will add functionality to this script to enable the following behavior:

  1. Arrêtez la collecte des données de mappage spatiale après avoir dépassé la limite de temps d’analyse (10 secondes).Stop collecting spatial mapping data after we exceed the scanning time limit (10 seconds).
  2. Traiter les données de mappage spatiale :Process the spatial mapping data:
    1. Utilisez SurfaceMeshesToPlanes pour créer une représentation plus simple du monde comme plans (murs, étages, plafonds, etc.).Use SurfaceMeshesToPlanes to create a simpler representation of the world as planes (walls, floors, ceilings, etc).
    2. Utilisez RemoveSurfaceVertices pour supprimer les triangles de surface qui se trouvent dans les limites du plan.Use RemoveSurfaceVertices to remove surface triangles that fall within plane boundaries.
  3. Générez un ensemble d’hologrammes dans le monde et placez-les sur des plans muraux et de plancher près de l’utilisateur.Generate a collection of holograms in the world and place them on wall and floor planes near the user.

Effectuez les exercices de codage marqués dans PlaySpaceManager.cs ou remplacez le script par la solution terminée ci-dessous :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;
        }
    }
}

Génération et déploiementBuild and Deploy

  • Avant de déployer sur HoloLens, appuyez sur le bouton de lecture dans Unity pour passer en mode lecture.Before deploying to the HoloLens, press the Play button in Unity to enter play mode.
  • Une fois le maillage de la salle chargé à partir du fichier, attendez 10 secondes avant le début du traitement sur le maillage de mappage spatial.After the room mesh is loaded from file, wait for 10 seconds before processing starts on the spatial mapping mesh.
  • Une fois le traitement terminé, les plans s’affichent pour représenter le plancher, les murs, le plafond, etc.When processing is complete, planes will appear to represent the floor, walls, ceiling, etc.
  • Une fois tous les plans détectés, un système solaire doit s’afficher sur une table de plancher près de l’appareil photo.After all of the planes have been found, you should see a solar system appear on a table of floor near the camera.
  • Deux affiches doivent également apparaître sur les murs près de l’appareil photo.Two posters should appear on walls near the camera too. Basculez vers l’onglet scène si vous ne les voyez pas en mode jeu .Switch to the Scene tab if you cannot see them in Game mode.
  • Appuyez de nouveau sur le bouton de lecture pour quitter le mode lecture.Press the Play button again to exit play mode.
  • Générez et déployez sur HoloLens, comme d’habitude.Build and deploy to the HoloLens, as usual.
  • Attendez la fin de l’analyse et du traitement des données de mappage spatiale.Wait for scanning and processing of the spatial mapping data to complete.
  • Une fois que vous voyez des plans, essayez de trouver le système solaire et les affiches dans votre monde.Once you see planes, try to find the solar system and posters in your world.

Chapitre 4-placementChapter 4 - Placement

ObjectifsObjectives

  • Déterminer si un hologramme peut tenir sur une surface.Determine if a hologram will fit on a surface.
  • Fournir des commentaires à l’utilisateur lorsqu’un hologramme ne peut pas être ajusté sur une surface.Provide feedback to the user when a hologram can/cannot fit on a surface.

InstructionsInstructions

  • Dans le panneau de la hiérarchie Unity, sélectionnez l’objet SpatialProcessing .In Unity's Hierarchy panel, select the SpatialProcessing object.
  • Dans le volet de l' inspecteur , recherchez les maillages des surfaces du composant plans (script) .In the Inspector panel, find the Surface Meshes To Planes (Script) component.
  • Remplacez la valeur de la propriété dessiner des plans par Nothing pour effacer la sélection.Change the Draw Planes property to Nothing to clear the selection.
  • Remplacez la propriété dessiner des plans par Wall, afin que seuls les plans muraux soient rendus.Change the Draw Planes property to Wall, so that only wall planes will be rendered.
  • Dans le panneau projet , cliquez sur le dossier scripts , double-cliquez sur Placeable.cs pour l’ouvrir dans Visual Studio.In the Project panel, Scripts folder, double-click on Placeable.cs to open it in Visual Studio.

Le script positionnable est déjà associé aux affiches et à la boîte de projection créées après la recherche du plan.The Placeable script is already attached to the posters and projection box that are created after plane finding completes. Tout ce que nous devons faire, c’est supprimer les marques de commentaire de code, et ce script va obtenir ce qui suit :All we need to do is uncomment some code, and this script will achieve the following:

  1. Déterminez si un hologramme est ajusté sur une surface par Raycasting à partir du centre et quatre coins du cube englobant.Determine if a hologram will fit on a surface by raycasting from the center and four corners of the bounding cube.
  2. Vérifiez la normale de la surface pour déterminer si elle est suffisamment lisse pour que l’hologramme se vide.Check the surface normal to determine if it is smooth enough for the hologram to sit flush on.
  3. Restituez un cube englobant autour de l’hologramme pour afficher sa taille réelle lors de son placement.Render a bounding cube around the hologram to show its actual size while being placed.
  4. Convertit une ombre sous/derrière l’hologramme pour montrer où il sera placé sur le plancher/le mur.Cast a shadow under/behind the hologram to show where it will be placed on the floor/wall.
  5. Restitue l’ombre en rouge, si l’hologramme ne peut pas être placé sur l’aire, ou vert, si possible.Render the shadow as red, if the hologram cannot be placed on the surface, or green, if it can.
  6. Réorienter l’hologramme pour qu’il s’aligne avec le type de surface (vertical ou horizontal) auquel il a une affinité.Re-orient the hologram to align with the surface type (vertical or horizontal) that it has affinity to.
  7. Placez en douceur l’hologramme sur la surface sélectionnée pour éviter le comportement de saut ou d’alignement.Smoothly place the hologram on the selected surface to avoid jumping or snapping behavior.

Supprimez les marques de commentaire de tout le code dans l’exercice de codage ci-dessous, ou utilisez cette solution terminée dans 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;
    }
}

Génération et déploiementBuild and Deploy

  • Comme précédemment, générez le projet et déployez sur HoloLens.As before, build the project and deploy to the HoloLens.
  • Attendez la fin de l’analyse et du traitement des données de mappage spatiale.Wait for scanning and processing of the spatial mapping data to complete.
  • Lorsque vous voyez le système solaire, pointez le curseur en regard de la zone de projection ci-dessous et effectuez un mouvement de sélection pour le déplacer.When you see the solar system, gaze at the projection box below and perform a select gesture to move it around. Lorsque la zone de projection est sélectionnée, un cube englobant est visible autour de la zone de projection.While the projection box is selected, a bounding cube will be visible around the projection box.
  • Déplacez-vous vers le regard d’un autre endroit de la pièce.Move you head to gaze at a different location in the room. La zone de projection doit suivre votre point de regard.The projection box should follow your gaze. Lorsque l’ombre sous la zone de projection devient rouge, vous ne pouvez pas placer l’hologramme sur cette surface.When the shadow below the projection box turns red, you cannot place the hologram on that surface. Lorsque l’ombre sous la zone de projection devient verte, vous pouvez placer l’hologramme en effectuant un autre mouvement Select.When the shadow below the projection box turns green, you can place the hologram by performing another select gesture.
  • Recherchez et sélectionnez l’une des affiches holographiques sur un mur pour la déplacer vers un nouvel emplacement.Find and select one of the holographic posters on a wall to move it to a new location. Notez que vous ne pouvez pas placer l’affiche à l’étage ou au plafond, et qu’il reste correctement orienté vers chaque mur au fur et à mesure que vous vous déplacez.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.

Chapitre 5-occlusionChapter 5 - Occlusion

ObjectifsObjectives

  • Détermine si un hologramme est bloqués par le maillage de mappage spatial.Determine if a hologram is occluded by the spatial mapping mesh.
  • Appliquez différentes techniques d’occlusion pour obtenir un effet amusant.Apply different occlusion techniques to achieve a fun effect.

InstructionsInstructions

Tout d’abord, nous allons autoriser le maillage de mappage spatial à occultait d’autres hologrammes sans Boucher le monde réel :First, we are going to allow the spatial mapping mesh to occlude other holograms without occluding the real world:

  • Dans le volet hiérarchie , sélectionnez l’objet SpatialProcessing .In the Hierarchy panel, select the SpatialProcessing object.
  • Dans le volet de l' inspecteur , recherchez le composant Gestionnaire d’espace de lecture (script) .In the Inspector panel, find the Play Space Manager (Script) component.
  • Cliquez sur le cercle situé à droite de la propriété matériau secondaire .Click the circle to the right of the Secondary Material property.
  • Recherchez et sélectionnez le matériel d' occlusion , puis fermez la fenêtre.Find and select the Occlusion material and close the window.

Nous allons ensuite ajouter un comportement spécial à la terre, afin qu’elle soit en surbrillance en bleu chaque fois qu’elle est bloqués par un autre hologramme (comme le soleil) ou par le maillage de mappage spatial :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:

  • Dans le panneau projet , dans le dossier hologrammes , développez l’objet SolarSystem .In the Project panel, in the Holograms folder, expand the SolarSystem object.
  • Cliquez sur terre.Click on Earth.
  • Dans le panneau inspecteur , recherchez le matériau de la terre (composant le plus bas).In the Inspector panel, find the Earth's material (bottom component).
  • Dans la liste déroulante nuanceur, remplacez le nuanceur par personnalisé > OcclusionRim.In the Shader drop-down, change the shader to Custom > OcclusionRim. Cela permet d’afficher une surbrillance bleue autour de la terre chaque fois qu’elle est bloqués par un autre objet.This will render a blue highlight around Earth whenever it is occluded by another object.

Enfin, nous allons activer un effet de vision x-ray pour les planètes dans notre système solaire.Finally, we are going to enable an x-ray vision effect for planets in our solar system. Nous devons modifier PlanetOcclusion.cs (qui se trouve dans le dossier Scripts\SolarSystem) afin d’obtenir les informations suivantes :We will need to edit PlanetOcclusion.cs (found in the Scripts\SolarSystem folder) in order to achieve the following:

  1. Déterminez si une planète est bloqués par la couche SpatialMapping (maillages et plans d’espace).Determine if a planet is occluded by the SpatialMapping layer (room meshes and planes).
  2. Affichez la représentation filaire d’une planète chaque fois qu’elle est bloqués par la couche SpatialMapping.Show the wireframe representation of a planet whenever it is occluded by the SpatialMapping layer.
  3. Masquer la représentation filaire d’une planète lorsqu’elle n’est pas bloquée par la couche SpatialMapping.Hide the wireframe representation of a planet when it is not blocked by the SpatialMapping layer.

Suivez l’exercice de codage dans PlanetOcclusion.cs ou utilisez la solution suivante :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);
            }
        }
    }
}

Génération et déploiementBuild and Deploy

  • Générez et déployez l’application dans HoloLens, comme d’habitude.Build and deploy the application to HoloLens, as usual.
  • Attendez la fin de l’analyse et du traitement des données de mappage spatiale (vous devriez voir apparaître des lignes bleues sur les murs).Wait for scanning and processing of the spatial mapping data to be complete (you should see blue lines appear on walls).
  • Recherchez et sélectionnez la zone de projection du système solaire, puis activez la case à cocher en regard d’un mur ou derrière un compteur.Find and select the solar system's projection box and then set the box next to a wall or behind a counter.
  • Vous pouvez afficher l’occlusion de base en masquant derrière les surfaces à l’homologue dans la zone d’affiche ou de projection.You can view basic occlusion by hiding behind surfaces to peer at the poster or projection box.
  • Si vous recherchez la terre, il doit y avoir un effet de surbrillance bleu lorsqu’il se trouve derrière un autre hologramme ou une autre surface.Look for the Earth, there should be a blue highlight effect whenever it goes behind another hologram or surface.
  • Regardez que les planètes se déplacent derrière le mur ou d’autres surfaces de la pièce.Watch as the planets move behind the wall or other surfaces in the room. Vous avez maintenant une vision x-ray et vous pouvez voir leurs squelettes filaires !You now have x-ray vision and can see their wireframe skeletons!

La finThe End

Félicitations !Congratulations! Vous avez maintenant terminé le mappage spatial 230 : spatial.You have now completed MR Spatial 230: Spatial mapping.

  • Vous savez comment analyser votre environnement et charger des données de mappage spatiale sur Unity.You know how to scan your environment and load spatial mapping data to Unity.
  • Vous comprenez les notions de base des nuanceurs et comment les documents peuvent être utilisés pour revisualiser le monde.You understand the basics of shaders and how materials can be used to re-visualize the world.
  • Vous avez appris les nouvelles techniques de traitement permettant de trouver des plans et de supprimer des triangles d’une maille.You learned of new processing techniques for finding planes and removing triangles from a mesh.
  • Vous avez pu déplacer et placer des hologrammes sur des surfaces qui ont été rendues logiques.You were able to move and place holograms on surfaces that made sense.
  • Vous avez connu différentes techniques d’occlusion et vous avez utilisé la puissance de la vision x-ray !You experienced different occlusion techniques and harnessed the power of x-ray vision!