Loupe

#HoloLens : passer d’une vue #UWP (XAML 2D) à un hologramme (vue 3D) et inversement

Dans cet article nous exploiterons pleinement le fait que les applications Unity et DirectX sont des applications UWP à part entière en montrant qu’il est possible de passer d’une vue XAML 2D à un hologramme et inversement.

Le principe de base sous-jacent

Il est possible, depuis un certain déjà, d’ouvrir une nouvelle fenêtre dans une application Windows Store (UWP ou Universal). C’est notamment ce que fait l’application Courrier pour permettre l’édition d’un mail dans une fenêtre à part.

Pour cela, il faut demander à l’application de créer une nouvelle vue(CoreApplicationView) c’est à dire l’association d’une fenêtre (CoreWindow) et d’un Dispatcher. Ce dernier point est intéressant car il faut faire très attention, lors de partage de ViewModel notamment, à se mettre sur le bon Dispatcher lorsque des composants liés à la vue sont utilisés. Le code, classique, va consister à ce snippet :

// récupération de l'id courant pour usage ultérieur
var appViewId = ApplicationView.GetForCurrentView().Id;

//Création de la nouvelle vue 
CoreApplicationView newCoreAppView = CoreApplication.CreateNewView();

await newCoreAppView.Dispatcher.RunAsync(
    Windows.UI.Core.CoreDispatcherPriority.Low,
     () =>
     {
         //récupération de la fenêtre crée
         Window window = Window.Current;
         ApplicationView newAppView = ApplicationView.GetForCurrentView();

         // création d'une nouvelle frame et navigation
         // vers une page
         var secondFrame = new Frame();
         window.Content = secondFrame;
         secondFrame.Navigate(typeof(MainPage));

         // activation de la nouvelle fenêtre
         window.Activate();

         // on se détache
         ApplicationViewSwitcher.TryShowAsStandaloneAsync(newAppView.Id,
             ViewSizePreference.UseMore, appViewId, ViewSizePreference.Default);
     });

 

Lorsque l’on exécute ce code, nous demandons implicitement au Framework XAML de se charger du rendu de notre nouvelle fenêtre. En effet, la méthode CreateNewView attend en paramètre un objet de type IFrameworkViewSource qui permet de construire un autre objet de type IFrameworkView. Si l’on ne passe rien à la méthode CreateNewView, il utilise alors automagiquement une instance d’IFrameworkViewSource dédié au rendu XAML.

 

Dans notre cas, l’affichage d’un hologramme, nous allons au contraire vouloir nous-mêmes nous charger du rendu. Pour cela, nous allons indiquer l’AppViewSource à utiliser lors de l’appel à la méthode CreateNewView.

 

Lancer une vue DirectX

Je prends ici comme exemple le sample de base de Microsoft permettant de faire le rendu d’un cube en 3D utilisant DirectX (JMM va bientôt faire un article plus poussé sur le sujet).

En créant un projet de type “HolographicDirectXApp”, j’ai donc tout ce qu’il faut pour afficher ce cube et notamment la partie initialisation qui me créé une instance dédiée d’IFrameworkView. Cet exemple utilise SharpDX, quelques classes C# et des shaders que je peux copier-coller directement dans un projet UWP XAML classique. J’ai aussi dû faire quelques copier-coller afin de récupérer correctement mes fichiers cso (les shaders compilés).

Capture

Une fois cela en place, il ne me reste plus qu’à utiliser le code précédent et de lui demander d’utiliser mon AppView DirectX.

Il faut cependant faire bien attention à :

  • garder une instance de mon AppViewSource : si elle est garbage collectée, la vue disparait…
  • Activer la fenêtre correspondant à cette nouvelle vue Direct X.
// récupération de l'id courant pour usage ultérieur
var appViewId = ApplicationView.GetForCurrentView().Id;

// on créé et on garde une instance d'AppViewSource
_appViewSource = new HolographicDirectXApp1.AppViewSource();

// on la donne à CoreApplication.CreateNewView
var newCoreAppView = CoreApplication.CreateNewView(_appViewSource);

// Activation et détachement de la nouvelle fenêtre, pas besoin
// de faire de Frame ici car on est dans DirectX.
await newCoreAppView.Dispatcher
    .RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low,
    async () =>
  {
      var newAppView = ApplicationView.GetForCurrentView();
      var res = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newAppView.Id,
             ViewSizePreference.Default, appViewId, ViewSizePreference.Default);

      newCoreAppView.CoreWindow.Activate();
  });

 

Et voici mon beau cube 3D qui tourne (très très très vite comme dirait Simon). Bien sûr, on passe en vision holographique classique et les hologrammes “du menu démarrer” ainsi que les autres applications UWP, disparaissent pour ne laisser que ma vue 3D apparaitre. Nous le verrons avec Unity, un retour à la vue UWP est possible.

Capture2

 

Lancer une vue Unity

Cela ne va pas vous surprendre, une application Unity est une application UWP XAML classique qui créé et configure sa surface de rendu DirectX pour afficher sa propre scène. Cela va donc être très similaire au travail déjà réalisé si ce n’est qu’il va falloir modifier le code “de base” d’unity pour ne pas passer tout de suite en vue 3D immersive mais rester dans notre application UWP XAML.

 

Pour me placer dans le bon contexte, je génère un projet Windows XAML et non pas Direct3D comme décrit dans l’article de Maxime. Je me retrouve donc dans ce contexte :

  • Une initialisation d’Unity faite directement dans la classe App.
  • Une page XAML MainPage vers laquelle on navigue au démarrage et qui termine l’initialisation.

 

Je vais alors devoir faire ces modifications :

  • Créer une nouvelle page XAML StartupPage vide.
  • Je navigue vers cette page dans le fichier App.Xaml.cs au lieu de MainPage : cela me permet de rester dans un contexte classique 2D et je peux construire mon application à partir de cette page.
  • Je supprime/commente l’instanciation et l’utilisation de la classe AppCallbacks du fichier App.xaml.cs,
  • J’initialise une instance d’AppCallbacks dans ma page MainPage.

 

La suite est assez simple puisqu’il me suffit de créer une nouvelle fenêtre et de naviguer vers la page MainPage pour ouvrir ma vue Holographique. Pour cela j’utilise exactement le même code que le premier extrait présenté dans cet article. La seule différence est que je m’abonne à l’activation de la fenêtre créée pour initialiser Unity et que je stocke l’identifiant de la fenêtre principale pour plus tard :

private async Task CreateNewHoloWindowAsync()
{
    var appViewId = MainAppViewId = ApplicationView.GetForCurrentView().Id;

    var _newCoreAppView = CoreApplication.CreateNewView();

    await _newCoreAppView.Dispatcher
        .RunAsync(CoreDispatcherPriority.Low,
      async () =>
      {
          var frame = new Frame();
          Window.Current.Content = frame;
          frame.Navigate(typeof(MainPage));

          var res = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(
              ApplicationView.GetForCurrentView().Id,
              ViewSizePreference.Default, appViewId,
              ViewSizePreference.Default);

          _newCoreAppView.CoreWindow.Activated += WindowActivated;

          _newCoreAppView.CoreWindow.Activate();
      });
}

private void WindowActivated(object sender, WindowActivatedEventArgs e)
{
    if (e.WindowActivationState == CoreWindowActivationState.CodeActivated
        || e.WindowActivationState == CoreWindowActivationState.PointerActivated)
    {
        AppCallbacks.Instance.SetInitialViewActive();
        // Only need to mark initial activation once so unregister ourself
        CoreWindow coreWindowSender = sender as CoreWindow;
        coreWindowSender.Activated -= WindowActivated;
    }
}

 

Il serait possible de factoriser et de rendre plus beau le code d’initialisation du player Unity au lieu d’utiliser tel quel la page MainPage mais cela impliquerait de refaire ces modifications à chaque changement de template fait par Unity. Nous en sommes déjà à la Beta 16 et cela serait donc un travail assez fastidieux pour le moment.

 

Revenir à l’application UWP

Pour revenir à notre vue 2D XAML de l’application il va falloir tout simplement utiliser l’ApplicationViewSwitcher pour lui demander de repasser à la fenêtre principale. Pour cela je fournis à mon code Unity une action (GoBackToEarth.CloseThisHolographicView) à appeler au moment opportun :

// on garde une capture du dispatcher
var dispatcher = Dispatcher;

GoBackToEarth.CloseThisHolographicView = () =>
{
    // On se place bien sur le Dispatcher de la fenêtre
    dispatcher.RunIdleAsync(async _ =>
    {
        // on repasse sur la fenêtre classique
        await ApplicationViewSwitcher.SwitchAsync(StartupPage.MainAppViewId);

        // on ferme la fenêtre pour éviter de la laisser
        // traîner dans la vue :)
        Window.Current.Close();
    });
};

 

Vous remarquerez que le contexte de la page appelante est gardé (je reviens bien à la même position de scroll).

 

 

Happy coding !

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus