Windows phone XAML/DirectX : Support de l’orientation

directX

Je suis actuellement en train de développer un jeu sur Windows Phone 8 avec Direct3D. J’ai commencé à le réaliser en full natif, mais j’ai rapidement réalisé que je voudrais rajouter une interaction avec l’OS pour certaines fonctionnalités. Je me suis donc tourné vers l’interop Xaml/DirectX grâce au contrôle xaml DrawingSurfaceBackgroundGrid.

Malheureusement pour moi, mon jeu est en mode paysage, et ce contrôle ne gère pas cette orientation. Et dans ce type de projet il n’y a pas la SwapChain, pour en appeler la méthode SetRotation.

Je vous propose donc de voir ensemble comment contourner ce problème.

La problématique

Pour expliquer le problème, je vous propose de commencer par créer un projet avec le template de base Xaml/DirectX (avec le DrawingSurfaceBackgroundGrid).

Création du projet

Dans la page Main.xaml il faut autoriser les deux orientations :

SupportedOrientations="PortraitOrLandscape"

Maintenant exécutons le projet pour voir ce qu’il se passe :

En mode portrait :
En mode portrait

En mode paysage :
En mode paysage

Pour mettre en exergue le problème, ajoutons un élément dans la page Main.xaml :

<DrawingSurfaceBackgroundGrid x:Name="DrawingSurfaceBackground" Loaded="DrawingSurfaceBackground_Loaded">
    <!-- Nous ajoutons ce contenu pour mettre en évidence le problème-->
    <Border Background="Red" HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Height="100" Margin="24">
        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">Xaml Content</TextBlock>
    </Border>
</DrawingSurfaceBackgroundGrid>

Changeons également la couleur des sommets du cube, en mettant sommet supérieur en rouge et les sommets inférieurs en bleu. Pour faire ceci rendons nous dans la méthode CreateDeviceResources du fichier CubeRenderer.cpp et modifions le tableau des sommets avec :

VertexPositionColor cubeVertices[] =
{
    {XMFLOAT3(-0.5f, -0.5f, -0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f)},
    {XMFLOAT3(-0.5f, -0.5f,  0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f)},
    {XMFLOAT3(-0.5f,  0.5f, -0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f)},
    {XMFLOAT3(-0.5f,  0.5f,  0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f)},
    {XMFLOAT3( 0.5f, -0.5f, -0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f)},
    {XMFLOAT3( 0.5f, -0.5f,  0.5f), XMFLOAT3(0.0f, 0.0f, 1.0f)},
    {XMFLOAT3( 0.5f,  0.5f, -0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f)},
    {XMFLOAT3( 0.5f,  0.5f,  0.5f), XMFLOAT3(1.0f, 0.0f, 0.0f)},
};

Voyons maintenant ce que l’on obtient…

En mode portrait:
portrait_2

En mode paysage :
paysage_2

On voit bien ici qu’il y a un problème. Le contrôle supporte bien l’orientation coté xaml, puisque le contenu enfant que l’on a ajouté s’adapte bien en fonction de l’orientation. Mais par contre l’affichage géré par Direct3D se fait toujours comme si l’on était en mode portrait.

La solution

Comme je vous l’ai dit en introduction, nous n’avons pas de SwapChain, nous ne pouvons donc pas lui appliquer de rotation.

Il va donc falloir le gérer dans notre phase de rendu. Nous allons tout d’abord appliquer une matrice de rotation à celle de projection utilisée lors du rendu. Puis nous détecterons le changement d’orientation pour mettre à jour cette matrice de rotation.

Appliquer la matrice de rotation

Commençons par ajouter, à la classe CubeRenderer, un membre qui nous permettra de stocker cette matrice.

DirectX::XMFLOAT4X4 m_orientationTransform3D;

Que l’on initialise avec la matrice identité (ce qui correspond à aucune transformation dans notre cas, on considère donc ici que le mode par défaut est le portrait) :

// Initialisation avec la matrice identité
m_orientationTransform3D = XMFLOAT4X4(
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
    );

Il nous suffit maintenant de multiplier la matrice de projection à celle que l’on vient de créer (dans la méthode CreateWindowSizeDependentResources de la classe CubeRenderer :

XMStoreFloat4x4(
        &m_constantBufferData.projection,
        XMMatrixTranspose(
            XMMatrixMultiply(
                // Attention bien la mettre la matrice d'orientation en premier !
                XMMatrixTranspose(XMLoadFloat4x4(&m_orientationTransform3D)),
                XMMatrixPerspectiveFovRH(fovAngleY, aspectRatio, 0.01f, 100.0f)
            )
        )
    );

Attention, la multiplication de matrice n’étant pas commutative dans la majeur partie des cas, l’ordre dans la fonction XMMatrixMultiply est important.

Si l’on exécute maintenant, aucun changement sera visible.

Maintenant, passons à la détection de l’orientation pour mettre à jour notre matrice.

Mise à jour de la matrice en fonction de l’orientation

Tant que nous sommes dans la classe CubeRenderer, commençons par exposer une méthode qui permet de mettre à jour notre matrice en fonction d’une orientation d’écran.

// Méthode de mise à jour de l'orientation d'affichage
void UpdateOrientation(Windows::Graphics::Display::DisplayOrientations orientation);

Avec l’implémentation suivante :

void CubeRenderer::UpdateOrientation(Windows::Graphics::Display::DisplayOrientations orientation)
{
    switch (orientation)
    {
    case Windows::Graphics::Display::DisplayOrientations::Landscape:
        m_orientationTransform3D = XMFLOAT4X4( // Rotation Z - 90 degrés
            0.0f, 1.0f, 0.0f, 0.0f,
            -1.0f, 0.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f
            );
        break;

    case Windows::Graphics::Display::DisplayOrientations::Portrait:
        m_orientationTransform3D = XMFLOAT4X4( // Rotation Z - 0 degré (identité)
            1.0f, 0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f
            );
        break;

    case Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped:
        m_orientationTransform3D = XMFLOAT4X4( // Rotation Z - 270 degrés
            0.0f, -1.0f, 0.0f, 0.0f,
            1.0f, 0.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f
            );
        break;

    case Windows::Graphics::Display::DisplayOrientations::PortraitFlipped:
        m_orientationTransform3D = XMFLOAT4X4( // Rotation Z - 180 degrés
            -1.0f, 0.0f, 0.0f, 0.0f,
            0.0f, -1.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f
            );
        break;
    default:
        throw ref new Platform::FailureException();
    }

    // Mise à jour des ressources dépendantes de la taille
    // de la fenêtre. Ce qui est le cas de la matrice de
    // projection
    CreateWindowSizeDependentResources();
}

Comme on peut le voir cette méthode n’est pas très compliquée, elle affecte la bonne rotation autour de l’axe Z en fonction de l’orientation donnée en paramètre. Puis on finit par appeler la méthode CreateWindowSizeDependentResources pour mettre à jour la matrice de projection.

Rendons-nous maintenant dans le composant WinRT Direct3DBackground situé dans le fichier <nom_du_projet>Component.h pour tout d’abord y stocker l’orientation :

Windows::Graphics::Display::DisplayOrientations m_orientation;

Puis pour y ajouter une méthode publique qui permettra de mettre à jour cette orientation, et de notifier notre renderer de ce changement :

inline void SetOrientation(Windows::Graphics::Display::DisplayOrientations orientation) { m_orientation = orientation; m_renderer->UpdateOrientation(orientation); }

A ce stade, nous avons fini avec la partie c++.

Revenons donc dans la page Main.xaml et abonnons nous à l’événement OrientationChanged de notre PhoneApplicationPage :

OrientationChanged="PhoneApplicationPage_OrientationChanged"

Puis dans le code-behind :

private void PhoneApplicationPage_OrientationChanged(object sender, OrientationChangedEventArgs e)
{
    m_d3dBackground.SetOrientation(ConvertOrientations(e.Orientation));
}

/// <summary>
/// Fonction helper pour convertir une PageOrientation en DisplayOrientations
/// </summary>
/// <param name="pageOrientation">Orientation de la page dans le type PageOrientation </param>
///  <returns>Equivalent de l'orientation dans le type DisplayOrientations </returns>
private DisplayOrientations ConvertOrientations(PageOrientation pageOrientation)
{
    switch(pageOrientation)
    {
        case PageOrientation.LandscapeLeft:
            return DisplayOrientations.Landscape;
        case PageOrientation.LandscapeRight:
            return DisplayOrientations.LandscapeFlipped;
        case PageOrientation.PortraitUp:
            return DisplayOrientations.Portrait;
        case PageOrientation.PortraitDown:
            return DisplayOrientations.PortraitFlipped;
        default:
            return DisplayOrientations.None;
    }
}

Exécutons notre projet voir ce que ça donne maintenant :

En mode portrait :
Aucun changement en portrait

En mode paysage :
Problème résolu en paysage

Et voilà ! Problème résolu. C’est dommage que ce ne soit pas supporté directement par le contrôle DrawingSurfaceBackgroundGrid, mais comme on vient de le voir ce n’est pas si compliqué à ajouter.

Vous trouverez les sources de l’exemple traité dans cet article ici.

J’espère que cet article vous a été utile. Je reviendrai sûrement sur d’autres problématiques rencontrées lors du développement de mon jeu.

Je vous dis donc à bientôt.

Nombre de vue : 74

AJOUTER UN COMMENTAIRE