Académique Documents
Professionnel Documents
Culture Documents
Guide de programmation
Programmez
des jeux vidéo 3D
avec
C#5 et WPF
Patrice REY
Tous les noms de produits ou marques cités dans ce livre sont
des marques déposées par leurs propriétaires respectifs.
La loi du 11 Mars 1957 n’autorisant aux termes des alinéas 2 et 3 de l’article 41, d’une
part, que les «copies ou reproductions strictement réservées à l’usage privé du copiste
et non destinées à une utilisation collective», et, d’autre part, que les analyses et les
courtes citations dans un but d’exemple et d’illustration, «toute représentation ou
reproduction intégrale, ou partielle, faite sans le consentement de l’auteur ou de ses
ayants droit ou ayant cause, est illicite» (alinéa 1er de l’article 40). Cette représentation
ou reproduction, par quelque procédé que ce soit, constituerait donc une contre-
façon sanctionnée par les articles 425 et suivants du Code Pénal.
(ISBN : 978-2-8106-1168-3)
Les structures de données illustrées avec WPF et C#4
(ISBN : 978-2-8106-1387-8)
La programmation graphique 2D de WPF 4
(ISBN : 978-2-8106-1199-7)
La programmation graphique 3D de WPF 4
(ISBN : 978-2-8106-2086-9)
Guide pratique de la modélisation 3D avec WPF 4
(ISBN : 978-2-8106-1308-3)
Développez des applications internet avec SILVERLIGHT 5
(ISBN : 978-2-8106-2222-1)
La 3D avec SILVERLIGHT 5
(ISBN : 978-2-8106-2512-3)
Le traitement d’images avec SILVERLIGHT 5
(ISBN : 978-2-8106-2615-1)
85 rue de vincennes
App 23 B
33000 BORDEAUX
e-mail : reypatrice@orange.fr
Table des matières
Le code source de programmation
est téléchargeable gratuitement
à l’adresse internet
http://www.reypatrice.fr
Avant-propos ........................................................................................ 9
Pour réaliser plus facilement des programmes en C# et XAML, il est préférable d’utiliser
un environnement de développement intégré ou IDE (Integrated Development
Environment). Cet IDE permet de développer, de compiler et d’exécuter un programme
dans un langage donné qui sera, dans ce livre, le C# (se prononce C Sharp) dans sa
version 5. Microsoft propose comme IDE soit Visual Studio 2012, dans sa version
payante avec 90 jours d’essai gratuit (figure A1), soit Visual Studio 2012 Express, dans
sa version allégée mais gratuite (figure A2).
Puissante interface IDE garantissant la production d’un code de qualité, Visual Studio
2012 intègre de nouvelles fonctionnalités qui simplifient le processus de développement
d’application, de la conception au déploiement.
Pour télécharger et installer l’environnement de développement intégré, ouvrez votre
navigateur et allez sur la page web suivante: http://msdn.microsoft.com/fr-fr/express/
aa975050.aspx
FIGURE A1
1
Avant-propos 11
FIGURE A2
Le contenu du livre
Tout le code source de cet ouvrage pourra être téléchargé gratuitement à l’adresse
web suivante:
http://www.reypatrice.fr
Apprentissage
de la
Modélisation
3D
1
Point, vecteur et matrice
dans l’espace 3D
Au sommaire de ce chapitre
• le repère des coordonnées 3D
• l’utilisation des structures Point3D et Vector3D
• l’utilisation des coordonnées euclidiennes et homogènes
• l’utilisation de la structure Point4D et les transformations en coordonnées
homogènes
• les opérations et les transformations sur la structure Matrix3D
18 Développez des applications 3D avec C#5 et WPF 4.5
L’espace de noms System.Windows.Media.Media3D (assembly PresentationCore.dll)
contient toutes les classes et toutes les structures qui supportent la représentation 3D.
Les fonctionnalités 3D permettent aux développeurs de dessiner, de transformer et
d’animer des graphiques 3D, à la fois en XAML et en code procédural. Les développeurs
peuvent aussi combiner de la 2D et des graphiques 3D pour créer des contrôles riches,
pour fournir des illustrations complexes de données et pour améliorer l’expérience
utilisateur de l’interface d’une application.
Dans ce chapitre, nous allons voir la définition et l’utilisation des structures Point3D,
Vector3D et Matrix3D, qui représentent les bases de la construction, en géométrie
analytique, des objets dans l’espace 3D.
Dans un système à 3 dimensions, un point est représenté par ses coordonnées selon
les 3 axes X, Y et Z. Dans le repère 3D utilisé par WPF, depuis l’origine O du repère, l’axe
X des abscisses positives part vers la droite, l’axe Y des ordonnées positives part vers le
haut, et l’axe Z des coordonnées positives part vers l’extérieur et devant.
Ces 3 axes peuvent être représentés au moyen des doigts d’une main: le pouce
représente l’axe des X, l’index représente l’axe des Y et le majeur représente l’axe des Z.
L’axe des X est généralement dirigé vers la droite et l’axe des Y est généralement dirigé
vers le haut. Pour la direction de l’axe des Z, WPF utilise le système «main droite» dans
lequel l’axe des Z est dirigé vers l’avant (il sort de l’écran).
Y
plan (Ox,Oy)
plan (Oy,Oz)
O
X
plan (Ox,Oz)
Z
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 19
2 - La structure Point3D
La localisation des points qui constituent une figure 3D dans un espace 3D, passe par
l’utilisation des coordonnées 3D. Le framework .NET 4.5 propose une structure nommée
Point3D. Cette structure Point3D représente un point de coordonnées x (abscisse), y
(ordonnée) et z dans l’espace 3D. La figure 1.1 énumère les propriétés et les méthodes
de cette structure. Cette structure Point3D expose les propriétés:
• X qui représente la coordonnée x du Point3D sur l’axe des X.
• Y qui représente la coordonnée y du Point3D sur l’axe des Y.
• Z qui représente la coordonnée z du Point3D sur l’axe des Z.
FIGURE 1.1
20 Développez des applications 3D avec C#5 et WPF 4.5
L’UserControl UtilisationPoint3d.xaml, dans le dossier chapitre_01, va nous permettre
de nous familiariser avec la pratique de la structure Point3D (figure 1.2).
FIGURE 1.2
Un point A (figure 1.3) de coordonnées (4,2,3), intitulé pt_a et de type Point3D, est
instancié en code procédural par Point3D pt_a = new Point3D(4,2,3). Les propriétés X,
Y et Z de la structure sont de type double.
FIGURE 1.3
Y
A(4,2,3)
4
X
3
Copyright 2013 Patrice REY
Z
Le premier choix du sélecteur x_select permet d’instancier 2 structures Point3D et
permet de tester leur égalité par la méthode héritée Equals ou bien par la surcharge de
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 21
l’opérateur «==». L’inégalité entre 2 structures Point3D est testée par la surcharge de
l’opérateur «!=». La figure 1.4 affiche les résultats obtenus.
FIGURE 1.4
A(4,2,3)
4 6
X
A(6,3,5)
3
Copyright 2013 Patrice REY
Z
5
FIGURE 1.6
La classe Point3DCollection est une collection typée d’objets Point3D. Elle est très utile
quand l’on souhaite stocker un ensemble de points, de type Point3D, pour effectuer
par exemple une visualisation d’un ensemble de points à un instant donné.
Pour instancier une nouvelle collection collect, de type Point3DCollection, on écrira
Point3DCollection collect = new Point3DCollection(). Et pour ajouter le point A(4,2,3) à
24 Développez des applications 3D avec C#5 et WPF 4.5
cette collection, on écrira
• soit collect.Add(new Point3D(4,2,3)) pour ajouter un nouveau point A.
• soit collect.Add(pt_a) si le point A(4,2,3) a été instancié par Point3D pt_a=new
Point3D(4,2,3).
L’énumération d’une collection Point3DCollection se réalise facilement par l’emploi
d’une boucle foreach qui énumère les objets Point3D contenus dans la collection.
private void TesterCollectionPoint() {
Point3D pt_a = new Point3D(2, 3, 4);
Point3D pt_b = new Point3D(1, 2, 3);
Point3D pt_c = new Point3D(2, -1, -3);
Point3DCollection collect = new Point3DCollection();
collect.Add(pt_a);
collect.Add(pt_b);
collect.Add(pt_c);
...
AfficherTexte(«Point3DCollection collect = new Point3DCollection()» + RC);
AfficherTexte(«collect.Add(pt_a)» + RC);
AfficherTexte(«collect.Add(pt_b)» + RC);
AfficherTexte(«collect.Add(pt_c)» + RC);
AfficherCalcul(«Test -> énumération de la collection:» + RC);
foreach (Point3D pt in collect) {
string etiq = «-> pt(« + pt.X.ToString() + «,» + pt.Y.ToString() + «,» +
pt.Z.ToString() + «)» + RC;
AfficherCalcul(etiq);
}
}
FIGURE 1.7
3 - La notion de vecteur
René Descartes (1596-1650) est l’un des plus grands savants et philosophes de
son temps, fondateur de la géométrie analytique, de l’application de l’algèbre à la
géométrie, et d’une approche de la connaissance fondée sur la méthode rationnelle.
Avec les encouragements de ses amis scientifiques (Mersenne, Beeckman, Mydorge,
Hortensius, Huygens, van Schooten), René Descartes publia son monumental traité
«Discours de la Méthode, pour bien conduire sa raison et chercher la vérité dans les
sciences», fondement de la démarche scientifique moderne (figure 1.8). Cet ouvrage
comporte trois appendices qui illustrent l’application de sa méthode, ce sont La
Dioptrique, Les Météores et La Géométrie. Ce traité fut publié à Leyden en 1637.
C’est dans son traité de géométrie que Descartes fit un apport décisif : il posa le premier
les principes de l’invariance, où fut soulevée pour la première fois la question de
référentiel pour observer et décrire les phénomènes. Pour René Descartes, l’existence
même d’un raisonnement mathématique et d’une façon ordonnée de penser, prouve
que l’homme, en suivant méthodiquement certaines règles, peut se convaincre d’avoir
atteint la vérité. L’ordre qu’il établit en géométrie des courbes repose sur l’emploi des
coordonnées.
FIGURE 1.8
26 Développez des applications 3D avec C#5 et WPF 4.5
La géométrie analytique est un outil nouveau qui permet de résoudre par le calcul un
problème de géométrie. Il est forgé par Descartes alors que celui-ci cherche à résoudre
un problème posé par Pappus à la fin du IIIe siècle et non résolu jusqu’alors. Il apporte
l’idée de remplacer chaque point par deux nombres qui seront ses coordonnées afin
de résoudre par le calcul un problème de géométrie.
Par définition, un vecteur est un objet mathématique, représenté par deux points A et
B, et qui possède 3 caractéristiques:
• une direction: c’est la droite (AB)
• un sens: celui qui va de A vers B
• une longueur: c’est la distance entre A et B
droite AB
A
En mathématiques, les mots «sens» et «direction» ne sont pas synonymes. Une
direction est définie à l’aide d’une droite. Des droites parallèles ont la même direction.
Un sens n’est défini qu’associé à une direction. Pour une direction il n’y a que deux sens
possibles. A
A sens de B vers A
D1
D2 D3
les droites D1, D2 et D3 définissent sur la direction définie par (AB), il y a le sens «A
la même direction vers B» et le sens «B vers A»
D’un point de vue modélisation des données, un vecteur s’apparente à un déplacement
Copyright 2013 Patrice REY
selon l’axe des abscisses et à un déplacement selon l’axe des ordonnées. Un vecteur
dans l’espace 2D sera matérialisé par ces déplacements, par une valeur réelle x (pour
les abscisses) et par une valeur réelle y pour les ordonnées.
La connaissance de ces 2 valeurs suffit pour matérialiser n’importe quel vecteur. Et à
partir de ces 2 valeurs, nous pouvons calculer facilement la longueur du vecteur. Il en
est de même dans l’espace 3D en rajoutant une coordonnée z.
les droites
CHAPITRE 1 D1, D2 et D3 définissent Point, vecteur
sur la direction définie par
et matrice dans(AB),l’espace
il y a le sens3D
«A
la même direction vers B» et le sens «B vers A» 27
la longueur L du vecteur AB
est égale à sa norme
c’est-à-dire :
déplacement distance = b
selon Y
A
déplacement selon X
O
distance = a
4 - La structure Vector3D
Le framework .NET 4.5 possède une structure Vector3D qui gère l’utilisation et la
manipulation des vecteurs dans l’espace 3D.
Un vecteur dans l’espace 3D est représenté par une structure Vector3D. Un vecteur
dans l’espace 3D, de type Vector3D, par définition, représente un déplacement dans
l’espace 3D. Cette structure expose les propriétés suivantes:
• X qui représente le composant x de cette structure.
• Y qui représente le composant y de cette structure.
• Z qui représente le composant z de cette structure.
• Length qui obtient la longueur de cette structure.
• LengthSquared qui obtient le carré de la longueur de cette structure.
La figure 1.9 montre un vecteur V dont ses composantes sont (5,3,3). Ce vecteur
part de l’origine O vers le point A(5,3,3). Un vecteur indique un déplacement par ses
composantes. Depuis l’origine O, le déplacement selon X est de 5, le déplacement
selon Y est de 3 et le déplacement selon Z est de 3.
De ce fait, on dit que le point A est l’image du point O par la translation de vecteur
(5,3,3). Il est important de garder à l’esprit qu’un vecteur est un déplacement dans
l’espace 3D. Il existe une infinité de vecteurs identiques à celui-ci.
28 Développez des applications 3D avec C#5 et WPF 4.5
FIGURE 1.9 Y vecteur de l’origine O à A(5,3,3)
A(5,3,3)
O V
X
FIGURE 1.11 Y
V(3,2,1)
B(4,3,3)
X
A(1,1,2)
Z
30 Développez des applications 3D avec C#5 et WPF 4.5
Avec les points A(1,1,2) et B(4,3,3), le vecteur V(3,2,1) permet de passer de A vers B et
le vecteur V(-3,-2,-1) permet de passer de B vers A.
private void TrouverVecteurEntre2Points() {
Point3D pt_a = new Point3D(1, 1, 2);
Point3D pt_b = new Point3D(4, 3, 3);
Vector3D vect_a_vers_b = pt_b - pt_a;
AfficherTexte(«Vector3D vect_a_vers_b = pt_b - pt_a» + RC);
Vector3D vect_b_vers_a = pt_a - pt_b;
AfficherTexte(«Vector3D vect_b_vers_a = pt_a - pt_b» + RC);
}
La méthode statique Point3D.Add permet d’additionner un vecteur à un point. Cela
permet de trouver les coordonnées d’un point B connaissant les coordonnées d’un
point A et celles d’un vecteur V. La figure 1.12 schématise géométriquement le résultat
recherché. La structure Vector3D possède une surcharge de l’opérateur «+», ce qui
permet d’écrire directement l’expression pt_b = pt_a + vect avec pt_a et pt_b qui sont
du type Point3D et vect qui est du type Vector3D. A noter qu’une deuxième surcharge
de l’opérateur permet d’écrire aussi pt_b = vect + pt_a.
FIGURE 1.12
Y
V(3,2,1)
X
A(1,1,2) Copyright 2013 Patrice REY
FIGURE 1.13
V(3,2,1)
X
B(1,1,2)
additionner le vecteur U au
vecteur V donne le vecteur
V(4,-2,0) vect_u_v
U(0,2,-3)
X
vect_u_v (4,0,-3)
Le produit vectoriel (figure 1.18) consiste à multiplier un vecteur par un scalaire (une
valeur de type double). Le vecteur se trouve alors augmenté en longueur mais sa
direction reste la même. Son sens peut être opposé si le scalaire multiplicateur est
négatif. La surcharge de l’opérateur «*» permet de multiplier un vecteur par une valeur
de type double. L’autre façon est d’utiliser la méthode statique Vector3D.Multiply
(figure 1.17).
FIGURE 1.17
X
V(4,2,-2)
U(2,1,-1)
Z
La longueur d’un vecteur est obtenue par la propriété Length et la longueur élevée au
carré est obtenue par la propriété LengthSquared. La figure 1.19 visualise le résultat
obtenu pour un vecteur U(3,3,3) et la figure 1.20 schématise ce vecteur U.
private void TrouverlongueurVecteur() {
Vector3D vect_u = new Vector3D(3, 3, 3);
AfficherTexte(«Vector3D vect_u = new Vector3D(3, 3, 3)» + RC);
AfficherCalcul(«-> longueur de vect_u = « + vect_u.Length.ToString() + RC);
AfficherCalcul(«-> longueur au carré de vect_u = « + vect_u.LengthSquared
.ToString() + RC);
}
FIGURE 1.19
36 Développez des applications 3D avec C#5 et WPF 4.5
FIGURE 1.20
Y
U(3,3,3)
3
X
La normalisation d’un vecteur consiste à trouver les coordonnées du vecteur ayant pour
longueur 1. La normalisation ne change ni la direction du vecteur, ni le sens du vecteur.
Elle ne fait que réduire le vecteur à une longueur de 1 en recalculant ses composantes
(x,y,z). Pour normaliser un vecteur, il suffit de lui appliquer la méthode Normalize. Par
exemple (figure 1.21), le vecteur U(3,3,3) a une longueur approximative de 5.19. Après
la normalisation, ses composantes sont (0.58,0.58,0.58) et sa longueur est égale à 1.
FIGURE 1.21
U(0,0,3)
V(2,0,0)
X
angle
Z
38 Développez des applications 3D avec C#5 et WPF 4.5
private void AngleEntreVecteur() {
Vector3D vect_u = new Vector3D(0, 0, 3);
Vector3D vect_v = new Vector3D(2, 0, 0);
AfficherTexte(«Vector3D vect_u = new Vector3D(0, 0, 3)» + RC);
AfficherTexte(«Vector3D vect_v = new Vector3D(2, 0, 0)» + RC);
double angle = Vector3D.AngleBetween(vect_u, vect_v);
AfficherTexte(«double angle = Vector3D.AngleBetween(vect_u, vect_v)» + RC);
AfficherCalcul(«-> angle entre vect_u et vect_v = « + angle.ToString()
+ « degrés» + RC);
}
Les notions de produit scalaire et de produit vectoriel sont très importantes en algèbre
linéaire. La méthode statique Vector3D.DotProduct permet de calculer le produit
scalaire de deux vecteurs et la méthode statique Vector3D.CrossProduct permet de
calculer le produit vectoriel de deux vecteurs.
Par définition, on appelle produit scalaire (euclidien) de deux vecteurs (x1,y1,z1) et
(x2,y2,z2) le réel noté et défini par = x1*x2 + y1*y2 + z1*z2. Si on a les
vecteurs (1,2,3) et (2,3,6), alors le produit scalaire est égal au réel:
= 1*2 + 2*3 + 3*6 = 26
Si les vecteurs sont de longueur non nulle, leur produit scalaire sera égal à 0 dans le
cas où ils sont perpendiculaires. Par exemple si on a (1,2,0) et (2,-1,0), le produit
scalaire sera nul.
Y
X
Copyright 2013 Patrice REY
Z
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 39
Le produit scalaire est utilisé en 3D par exemple pour déterminer si une facette
rectangulaire dans l’axe de visée d’une caméra montre sa face avant ou bien sa face
arrière (face cachée). Supposons que l’axe de visée d’une caméra soit représenté par
le vecteur (-3,-3,-3) et qu’une normale à une facette rectangulaire soit représentée
par le vecteur (0,0,2), alors le produit scalaire sera négatif. Si la normale à la facette
rectangulaire est représentée par le vecteur (0,0,-2), alors le produit scalaire sera
positif.
Dans ce cas précis, un produit scalaire négatif permet de dire que la face regardée est
la face avant, et un produit scalaire positif permet de dire que la face regardée est la
face arrière (face cachée).
X
(0,0,2)
Z (-3,-3,-3)
première troisième
coordonnée deuxième coordonnée
y1*z2 - y2*z1 coordonnée x1*y2 - x2*y1
z1*x2 - z2*x1
Avec un produit vectoriel, on peut définir par exemple la normale à une facette
rectangulaire. Une facette rectangulaire est composée par les points A, B, C et D. Le
vecteur (3,0,0) sert de support à AB, et le vecteur (0,2,0) sert de support à AB. Le
produit vectoriel représente la normale à la face et ses composantes sont égales
à (0,0,6).
D C
face
(0,2,0) avant
A
B
(3,0,0)
X
le vecteur représentant la
normale (0,0,6)
Z
La figure 1.23 visualise les résultats obtenus lors des calculs de produit scalaire et de
produit vectoriel.
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 41
FIGURE 1.23
La notation P2=M.P1+T veut dire que P2 est obtenu en multipliant P1 par la matrice
M puis en lui ajoutant la matrice T. La transformation affine a pour particularité de
conserver le parallélisme des droites mais de ne pas préserver ni les longueurs, ni les
angles.
La figure 1.24 visualise le point P1(2,0,4) qui est transformé en P2(6,1,2) par une
translation T(Tx=4, Ty=1, Tz=-2).
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 43
FIGURE 1.24
Y
Tx=4
Ty=1
Tz=-2
X
P2(6,1,2)
Z P1(2,0,4)
La figure 1.25 visualise une mise à l’échelle uniforme de valeur 2 avec S(Sx=2,Sy=2,Sz=2).
Le point P1(2,0,2) donne par cette mise à l’échelle le point P2(4,0,4).
FIGURE 1.25
Sx=2
Sy=2
Sz=2
P1(2,0,2)
P2(4,0,4)
Copyright 2013 Patrice REY
La réflexion, dit aussi effet miroir, est un cas particulier de la mise à l’échelle. La figure
suivante visualise la matrice pour un effet miroir selon l’axe des Y (on trouve x2=-x1,
y2=y1 et z2=z1).
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 45
La figure suivante visualise la matrice pour un effet miroir selon l’axe des Z (on trouve
x2=x1, y2=-y1 et z2=z1).
La figure suivante visualise la matrice pour un effet miroir selon l’axe des X (on trouve
x2=x1, y2=y1 et z2=-z1).
46 Développez des applications 3D avec C#5 et WPF 4.5
La matrice de rotation pour faire pivoter les points d’un angle α autour de l’axe des X
est la suivante:
Les coordonnées du point P2(x2,y2,z2) sont obtenues par le calcul matriciel des
coordonnées du point P1(x1,y1,z1) par la matrice de rotation:
Le calcul matriciel donne, pour un point P1(x1,y1,z1), un point P2(x2,y2,z2) avec x2=x1,
y2=y1*cos(α)-z1*sin(α) et z2=y1*sin(α)+z1*cos(α). La figure 1.26 visualise la rotation
d’un segment avec α=-90°. Le point A1(0,0,2) est transformé en A2(0,2,0) et le point
C1(6,0,2) est transformé en C2(6,2,0).
FIGURE 1.26
Y
B2(0,5,0) D2(6,5,0)
A1(0,0,2) C1(6,0,2)
Z B1(0,0,5) D1(6,0,5)
De la même façon, la matrice de rotation pour faire pivoter les points d’un angle α
autour de l’axe des Y est la suivante:
48 Développez des applications 3D avec C#5 et WPF 4.5
Les coordonnées du point P2(x2,y2,z2) sont obtenues par le calcul matriciel des
coordonnées du point P1(x1,y1,z1) par la matrice de rotation:
Et la matrice de rotation pour faire pivoter les points d’un angle α autour de l’axe des
Z est la suivante:
Les coordonnées du point P2(x2,y2,z2) sont obtenues par le calcul matriciel des
coordonnées du point P1(x1,y1,z1) par la matrice de rotation:
Le cisaillement est une opération qui permet de déformer les objets. On distingue trois
types de cisaillement: Cx selon l’axe des X, Cy selon l’axe des Y et Cz selon l’axe des Z.
La matrice pour un cisaillement selon l’axe des X est la suivante:
Les coordonnées du point P2(x2,y2,z2) sont obtenues par le calcul matriciel des
coordonnées du point P1(x1,y1,z1) par la matrice de cisaillement:
La figure 1.27 visualise un cisaillement selon l’axe des X avec Cy=2 et Cz=2 pour une
figure représentant un carré. D’après l’équation matricielle, un point A1(1,2,0) donnera
un point A2(5,2,0) avec Cy=2 et Cz=2.
50 Développez des applications 3D avec C#5 et WPF 4.5
FIGURE 1.27
Y
Cy=2
Cz=2
A1(1,2,0) A2(5,2,0)
De la même façon, on aura la matrice pour un cisaillement selon l’axe des Y qui sera la
suivante:
Les coordonnées du point P2(x2,y2,z2) sont obtenues par le calcul matriciel des
coordonnées du point P1(x1,y1,z1) par la matrice de cisaillement:
donc x2 = x1
Et on aura la matrice pour un cisaillement selon l’axe des Z qui sera la suivante:
Les coordonnées du point P2(x2,y2,z2) sont obtenues par le calcul matriciel des
coordonnées du point P1(x1,y1,z1) par la matrice de cisaillement:
Les mathématiques des images par ordinateur sont intimement liées à la multiplication
matricielle. Comme nous venons de le voir dans le paragraphe précédent, la translation
d’un point ne correspond pas directement à une multiplication matricielle, parce que
52 Développez des applications 3D avec C#5 et WPF 4.5
la translation n’est pas une transformation linéaire. Pour contourner cette difficulté, il
est classique d’introduire ce que l’on appelle les coordonnées homogènes.
Dans le système des coordonnées homogènes, on introduit une quatrième coordonnée
w, différente de zéro. Un point P(x,y,z) en coordonnées euclidiennes s’écrira P(x/w,y/
w,z/w,w) en coordonnées homogènes.
Si w=1 par exemple, alors les coordonnées du point P seront P(x,y,z,1). Ce changement
consistant à passer d’un triplet à un quadruplet de coordonnées s’appelle une
homogénéisation. Le cas où w est égale à zéro représente les points à l’infini.
De ce fait, les transformations 3D seront représentées par des matrices de 4x4. Une
matrice unique de 4x4 pourra désormais effectuer toutes les transformations affines et
non affines (translation, rotation, mise à l’échelle, cisaillement). Il sera possible aussi de
rassembler sous une seule matrice, une composition de transformations simplement
en effectuant une multiplication des matrices.
Cette matrice de 4x4 permet de réaliser des transformations affines ou non affines. Le
calcul des coordonnées (x2,y2,z2) en fonction des coordonnées (x1,y1,z1) se fera par le
calcul matriciel de la façon suivante:
Copyright 2013 Patrice REY
Dans la matrice de transformation M, les composantes m11, m12, m13, m21, m22,
m23, m31, m32 et m33 seront utilisées pour effectuer des rotations, des mises à
l’échelle et des cisaillements, et les composantes m14, m24 et m34 seront utilisées
pour la translation.
composantes pour la rotation, la composantes pour la
mise à l’échelle et le cisaillement translation
En 3D, une rotation fait tourner un ensemble de points ou d’objets d’un angle α autour
d’un axe de rotation. Contrairement à la rotation dans le plan, l’axe autour duquel
s’effectue la rotation 3D peut être une droite quelconque dans l’espace. Il sera alors
nécessaire de décomposer la rotation en trois composantes: la rotation autour de l’axe
des X, la rotation autour de l’axe des Y et la rotation autour de l’axe des Z, ce qui
donnera lieu à trois matrices de rotation différentes. Comme dans le cas de la 2D, la
rotation s’effectue toujours par rapport à l’origine.
La matrice de rotation utilisée pour une rotation d’un angle α autour de l’axe des X
donnera x2=x1, y2=y1*cos(α)-z1*sin(α) et z2=y1*sin(α)+z1*cos(α).
La matrice de rotation utilisée pour une rotation d’un angle α autour de l’axe des Y
donnera x2=x1*cos(α)+z1*sin(α), y2=y1 et z2=-x1*sin(α)+z1*cos(α).
La matrice de rotation utilisée pour une rotation d’un angle α autour de l’axe des Z
donnera x2=x1*cos(α)-y1*sin(α), y2=x1*sin(α)+y1*cos(α) et z2=z1.
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 55
La mise à l’échelle modifie la taille d’un objet par rapport à l’origine en l’agrandissant
ou en le réduisant. La matrice de transformation pour une mise à l’échelle donnera
x2=x1*Sx, y2=y1*Sy et z2=z1*Sz.
Le cisaillement qui permet de déformer les objets pourra se faire selon les trois axes.
D’où trois matrices de transformation concernant le cisaillement. Suivant l’axe des X, la
matrice de cisaillement donnera x2=x1+y1*Cy+z1*Cz, y2=y1 et z2=z1.
Dans WPF, la structure Point4D permet de gérer des points de type Point3D dans le
système des coordonnées homogènes. Elle est spécifiquement utilisée pour accomplir
des transformations avec des matrices 3D non affines. Elle expose les propriétés
suivantes:
• X qui définit la coordonnée suivant l’axe des X.
• Y qui définit la coordonnée suivant l’axe des Y.
• Z qui définit la coordonnée suivant l’axe des Z.
• W qui définit la quatrième coordonnée dans le système des coordonnées
homogènes.
La structure Point4D possède un ensemble de méthodes pour accomplir des
opérations mathématiques variées sur des objets Point4D. Les méthodes suivantes
sont généralement les plus utilisées:
• Point4D.Add qui est une méthode statique qui ajoute une structure Point4D à un
Point4D.
• Point4D.Substract qui est une méthode statique qui soustrait une structure Point4D
à une autre structure Point4D.
• Point4D.Multiply qui est une méthode statique qui transforme la structure Point4D
spécifiée par la structure Matrix3D indiquée.
• Offset qui déplace la structure Point4D de la valeur spécifiée.
• Point4D.Parse qui est une méthode statique qui convertit une représentation
Copyright 2013 Patrice REY
6 - La structure Matrix3D
La structure Matrix3D est une structure qui représente une matrice 4x4 dans le système
des coordonnées homogènes. Ses composantes sont les suivantes:
Dans la matrice de WPF, les lignes deviennent des colonnes. Le calcul matriciel se fait
donc de la façon suivante:
x2 = cos(-90)*5 + 0*0 + sin(-90)*0 + 0*1
=0
y2 = 0*5 + 1*0 + 0*0 + 0*1 = 0
z2 = -sin(-90)*5 + 0*0 + cos(-90)*0 + 0*1
=5
On retrouve bien les coordonnées de P2 qui sont (0,0,5). Les propriétés M11, M12, M13,
M21, M22, M23, M31, M32 et M33 sont donc réservées pour les transformations de
rotation, mise à l’échelle et inclinaison (ou cisaillement). Les propriétés OffsetX, OffsetY
et OffsetZ sont réservées à la translation.
composantes pour la rotation, la
mise à l’échelle et le cisaillement
Nous définissons une matrice mat1 de type Matrix3D qui représente une translation
de Tx=4 suivant l’axe des X, de Ty=2 suivant l’axe des Y et de Tz=1 suivant l’axe des Z.
60 Développez des applications 3D avec C#5 et WPF 4.5
Matrix3D mat1 = new Matrix3D(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
4, 2, 1, 1);
La propriété HasInverse nous retourne un booléen pour signifier si la matrice est
inversible. Par définition, une matrice M est inversible si il existe une matrice M-1 tel
que M*M-1=Mid avec Mid qui est la matrice identité et M-1 qui est la matrice inversée.
Dans la littérature, une matrice non inversible est appelée une matrice singulière, et
une matrice inversible est appelée une matrice non singulière ou régulière.
Si notre matrice mat1 est inversible alors on instancie une matrice mat2 en lui affectant
les valeurs de mat1. Puis on lui applique la méthode Invert qui calcule la matrice inverse.
if (mat1.HasInverse == true) {
x_infos.Text += «matrice mat1 est inversible» + RC;
mat2 = mat1;
mat2.Invert();
...
}
Pour vérifier que la matrice mat2 est bien une matrice inversée de la matrice mat1, on
instancie une nouvelle matrice mat3 et on lui affecte le produit matriciel de mat1 par
mat2.
if (mat1.HasInverse == true) {
...
Matrix3D mat3 = new Matrix3D();
mat3 = mat1 * mat2;
...
}
Il est à noter que la structure Matrix3D possède une surcharge des opérateurs qui
sont:
• * (multiplication) qui multiplie les matrices spécifiées.
• == (égalité) qui compare l’égalité exacte de deux instances de Matrix3D.
• != (inégalité) qui compare l’inégalité exacte de deux instances de Matrix3D.
Le code procédural nous donne le calcul. Si nous effectuons le calcul à la main pour
Copyright 2013 Patrice REY
vérifier que le produit des matrices donne bien la matrice identité, ce calcul se fait de
la façon suivante: les deux matrices sont des matrices 4x4, le produit sera une matrice
4x4 et chacune des composantes résultera du calcul d’une ligne par une colonne.
La figure 1.29 visualise le détail complet du calcul des 16 composantes N1 à N16 dans
cette multiplication de matrices.
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 61
FIGURE 1.29 matrice matrice
matrice inversée identité
C1 C2 C3 C4 N1 = L1*C1 = 1*1+0*0+0*0+0*(-4)=1
N2 = L1*C2 = 1*0+0*1+0*0+0*(-2)=0
N3 = L1*C3 = 1*0+0*0+0*1+0*(-1)=0
N4 = L1*C4 = 0
N5 = L2*C1 = 0
N6 = L2*C2 = 1
N7 = L2*C3 = 0
N8 = L2*C4 = 0
N9 = L3*C1 = 0
L1 N10 = L3*C2 = 0
L2 N11 = L3*C3 = 1
N12 = L3*C4 = 0
L3
N13 = L4*C1 = 0
L4 N14 = L4*C2 = 0
N15 = L4*C3 = 0
N16 = L4*C4 = 1
Comme le visualise la figure 1.30, nous avons un triangle repéré par les points P1(3,0,1),
P2(3,0,5) et P3(3,1,5). Nous allons appliquer à ces trois points la matrice mat1 de façon
à définir le triangle après la transformation (repéré par les points P1t, P2t et P3t).
FIGURE 1.30 Y
P1t
P3t
P1
X
P2t
P3
Z
P2
62 Développez des applications 3D avec C#5 et WPF 4.5
On instancie des points p1, p2 et p3 de type Point4D, en coordonnées homogènes, en
leur affectant les valeurs des coordonnées euclidiennes de P1, P2 et P3. Pour obtenir
les coordonnées de P1t, on instancie une nouvelle structure p1t de type Point4D et on
lui affecte le produit de p1 par la matrice mat1 (opérateur * surchargé pour la structure
Point4D).
Point4D p1 = new Point4D(3, 0, 1, 1);
x_infos.Text += «P1: = « + p1.ToString() + RC;
Point4D p1_t = new Point4D();
p1_t = p1 * mat1;
x_infos.Text += «P1t = P1 * mat1 « + p1_t.ToString() + RC;
Point4D p2 = new Point4D(3, 0, 5, 1);
x_infos.Text += «P2: = « + p2.ToString() + RC;
Point4D p2_t = new Point4D();
p2_t = p2 * mat1;
x_infos.Text += «P2t = P2 * mat1 « + p2_t.ToString() + RC;
Point4D p3 = new Point4D(3, 1, 5, 1);
x_infos.Text += «P3: = « + p3.ToString() + RC;
Point4D p3_t = new Point4D();
p3_t = p3 * mat1;
x_infos.Text += «P3t = P3 * mat1 « + p3_t.ToString() + RC;
D’un point de vue mathématique, comme nous avons pu le voir, les matrices 3D de
WPF sont des matrices transposées par rapport à celles utilisées dans l’algèbre linéaire
classique. Le détail du calcul des positions de P1t en fonction de P1 et de la matrice
mat1 est le suivant:
calcul de y2
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 63
calcul de z2
calcul de w
Il est important d’effectuer le calcul à la main lors de l’apprentissage car cela permet
de s’entraîner au calcul matriciel, et dès fois, cela permet de corriger des idées reçues.
structure mat_orig). Nous obtenons comme résultat une matrice dont les composantes
sont 1,2,3,4,2,1,0,0,0,0,1,0,401,352,503,401.
Matrix3D mat_trans_prepend = mat_orig;
mat_trans_prepend.TranslatePrepend(new Vector3D(100, 150, 200));
AfficherMatriceTexte(mat_trans_prepend, «mat_trans_prepend:
mat_trans.TranslatePrepend(new Vector3D(100,150,200))»);
Si nous effectuons le calcul à la main pour vérification, nous obtenons bien le résultat
CHAPITRE 1 Point, vecteur et matrice dans l’espace 3D 67
trouvé.
Comme ce quaternion est normalisé, on peut construire la matrice de rotation qui est
Au sommaire de ce chapitre
• définition et caractéristique de la projection perspective
• définition et caractéristique de la projection parallèle
• utilisation pratique de la matrice de projection perspective
• utilisation pratique de la matrice de projection parallèle
72 Programmez des jeux vidéo 3D avec C#5 et WPF
En infographie, les projections transforment les points d’un système de coordonnées
de dimension 3 en des points d’un système de coordonnées de dimension 2. Les objets
3D sont projetés dans un plan 2D, permettant de visualiser à un moment donné le
rendu d’une scène 3D.
Nous allons aborder au cours de ce chapitre les types de projection (perspective et
parallèle) en les mettant en œuvre pour se rendre compte de leurs différences.
Le rendu d’une scène 3D consiste à obtenir une visualisation réaliste de la scène dans
un plan de visualisation 2D. La projection d’un objet 3D est définie par un ensemble
de rayons de projection droits, appelés des projecteurs, qui partent d’un centre de
projection, passent par chaque point de l’objet, et intersectent un plan de projection.
Les intersections des rayons avec le plan de projection forment la projection de
l’objet. Le centre de projection se situe généralement à une distance finie du plan de
projection. Pour certains types de projections, il est pourtant pratique de considérer
un centre de projection qui tend à être infiniment loin. Les figures 2.1 et 2.2 visualisent
deux projections différentes d’un segment de droite.
FIGURE 2.1 A
A’
B’ plan de projection
les projecteurs
centre de projection
A’
B’ plan de projection
les projecteurs
centre de projection
à l’infini
La classe de projections que nous étudions ici est connue sous le nom de projections
géométriques planes, puisque la projection se produit sur une surface plane et non
sur une surface courbée, et que les projecteurs sont des droites et non des courbes. A
contrario, les projections cartographiques ne sont ni planes ni géométriques.
Les projections (terme usuellement employé pour les projections géométriques) sont
essentiellement de deux types: perspective et parallèle. Leur distinction revient à la
relation du centre de projection avec le plan de projection.
Si la distance entre le centre et le plan est finie, la projection est alors une projection
perspective. Si la distance entre le centre et le plan est infinie, la projection est alors
une projection parallèle. Dans une projection perspective, le centre de projection est
explicitement spécifié. Dans une projection parallèle, la direction de projection est
explicitement spécifiée.
Les projections perspectives de tout ensemble de lignes parallèles qui ne sont pas
parallèles au plan de projection convergent vers un point de fuite. Le point de fuite
peut être considéré comme la projection d’un point à l’infini. Il existe donc un nombre
infini de points de fuite dont chacun représente une direction dans laquelle une droite
peut être orientée. Si l’ensemble des lignes est parallèle à l’un des axes principaux, le
point de fuite est dit point de fuite axial (ou principal). Il existe au plus trois points de
fuite axiaux correspondant au nombre d’axes que le plan de projection intersecte.
Par exemple, si le plan de projection n’intersecte que l’axe Z (et lui est donc
74 Programmez des jeux vidéo 3D avec C#5 et WPF
perpendiculaire), l’axe Z seulement possède un point de fuite axial car les droites
parallèles à l’axe X ou à l’axe Y sont aussi parallèles au plan de projection et n’ont donc
pas de point de fuite.
Les projections perspectives sont classées selon leur nombre de points de fuite
principaux, et donc selon le nombre d’axes qui traversent le plan de projection. La
figure 2.3 visualise une projection perspective d’un cube avec un point de fuite. Dans
cette projection, les lignes parallèles aux axes X et Y ne convergent pas alors que celles
parallèles à l’axe Z convergent.
FIGURE 2.3 point de fuite sur l’axe Z
Z
La figure 2.4 visualise la construction d’une perspective à deux points de fuite
principaux. Les lignes parallèles à l’axe Y ne convergent pas dans la projection. Ce type
de perspective est couramment employé dans les dessins d’architecture, d’ingénierie
et de conception industrielle.
FIGURE 2.4 Y Copyright 2013 Patrice REY
point de fuite
point de fuite sur l’axe Z
sur l’axe X
Z X
CHAPITRE 2 Les projections 75
La figure 2.5 visualise une perspective à trois points de fuite. Ce type de perspective
n’est pas très utilisé car il n’ajoute que peu de réalisme supplémentaire.
FIGURE 2.5 Y
point de fuite
sur l’axe Z
point de fuite
sur l’axe X
Les projections parallèles peuvent être classées selon deux types: suivant la relation
entre la direction de projection et suivant la normale du plan de projection. Pour les
projections parallèles orthonormales, ces directions sont soit identiques soit dans le
sens inverse, la direction de projection est donc perpendiculaire au plan de projection.
En projections parallèles obliques, ce n’est pas le cas.
Les projections orthonormales les plus connues sont la projection de façade, la
projection de plan, et la projection de côté. Pour toutes ces projections, le plan de
projection est perpendiculaire à un axe principal qui représente donc la direction de
projection.
La figure 2.6 montre la construction de ces trois types de projections qui sont souvent
utilisées dans les dessins d’ingénierie pour représenter des pièces, des assemblages et
des structures. Les projections orthonormales axonométriques utilisent des plans de
projection qui ne sont pas perpendiculaires à un axe principal. Elles représentent donc
plusieurs faces d’un même objet à la fois. Le parallélisme des droites est préservé mais
pas les angles.
La projection isométrique est une projection axonométrique couramment utilisée.
76 Programmez des jeux vidéo 3D avec C#5 et WPF
La normale du plan de projection, c’est-à-dire la direction de projection, a des angles
égaux avec chaque axe principal.
FIGURE 2.6 plan de
projection de
plan
plan de projection
de côté
plan de
projection de
façade
projection projection
projection projection avec 1 point de avec 3 points
orthographique oblique fuite de fuite
projection de
projection de cabinet projection
plan
Copyright 2013 Patrice REY
Pour effectuer un rendu de la scène 3D, il faut positionner une caméra dans la scène.
La figure 2.8 visualise une caméra perspective avec son plan de projection et son
volume d’observation. Tous les points des objets 3D qui se trouvent dans le volume
d’observation, vont être projetés dans le plan de projection. Le point projeté sera à
l’intersection du plan de projection et de la droite qui passe par le centre de projection
et par le point de l’objet 3D.
FIGURE 2.8
plan de
projection
haut
Y gauche
Z
bas droit
X
centre de
projection
(caméra) plan de
près volume
d’observation
plan de loin
plan de
projection
hauteur
Y
largeur
Z
angle de
X vue
plan de loin
centre de
projection
(caméra) volume
d’observation
78 Programmez des jeux vidéo 3D avec C#5 et WPF
En fixant un angle de vue, une distance du plan de projection (distance entre le plan de
projection et le centre de projection) et une distance du plan de loin (distance entre le
centre de projection et le plan de loin), les points en 3D dans le volume d’observation
peuvent être projetés dans le plan de projection.
Le rendu de la scène 3D est donc une projection géométrique plane 2D. De plus, la
caméra (figure 2.9) est positionnée dans l’espace 3D. Elle vise un endroit de l’espace
par son axe Z (c’est la direction du regard ou Look Direction). Elle peut aussi pivoter
sur elle-même d’un angle dans le plan XY (c’est la direction du haut ou Up Direction).
Avec un plan de projection perpendiculaire à l’axe Z de la caméra, si la direction du
haut tourne, les points projetés sur le plan de projection seront à des emplacements
différents.
FIGURE 2.9
direction du
haut (up
direction) plan de
projection direction du
regard (look
Y direction)
Z
angle de
vue
centre de
projection
(caméra) X
volume
d’observation
plan de loin
Différents contrôles de type Slider permettent de régler, dans des plages de valeurs,
la position de la caméra dans l’espace 3D, la direction du regard de la caméra et
l’inclinaison (direction du haut) de la caméra.
Le cube possède six faces de couleurs différentes. Il y a six boutons qui permettent
d’effectuer un réglage prédéfini de façon à voir le cube de face, de droite, de gauche, de
dessus, de dessous et par l’arrière. Un dernier bouton permet de revenir à la position
CHAPITRE 2 Les projections 79
initiale. Les axes sont repérés par des couleurs: rouge pour l’axe des X, bleu pour l’axe
des Y et vert pour l’axe des Z.
FIGURE 2.10
La figure 2.11 montre le cube quand on clique sur le bouton face gauche (repère 1) et
quand on clique sur le bouton face arrière (repère 2).
80 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 2.11
Pp(xp,yp,zp)
X Y
P(x,y,z) P(x,y,z)
Pp(xp,yp,zp) Pp(xp,yp,zp)
Z Z
d d
Voyons le cas de la projection d’un point P sur un plan de projection qui est parallèle
au plan XY, et se situe en z=d. Afin de calculer Pp=(xp,yp,zp), c’est-à-dire la projection
perspective de P(x,y,z) sur le plan de projection en z=d, nous utilisons les triangles
similaires représentés dans la figure 2.12 et nous trouvons les rapports:
Pour effectuer un rendu de la scène 3D, il faut positionner une caméra dans la scène.
La figure 2.13 visualise une caméra parallèle avec son plan de projection et son
volume d’observation. Tous les points des objets 3D qui se trouvent dans le volume
d’observation, vont être projetés dans le plan de projection. Le point projeté sera à
l’intersection du plan de projection et de la droite qui passe par le centre de projection
et par le point de l’objet 3D. Comme dans la projection parallèle, le centre de projection
de la caméra se trouve à l’infini, la droite qui sert à trouver le point projeté est parallèle
à l’axe Z de la caméra.
FIGURE 2.13 plan de
projection
haut
gauche
Z
X droit
centre de bas
projection à
l’infini (caméra)
plan de volume
près d’observation plan de
plan de
projection loin
hauteur
X largeur
centre de plan de
projection à près plan de
l’infini (caméra) volume loin
d’observation
86 Programmez des jeux vidéo 3D avec C#5 et WPF
De plus, la caméra (figure 2.13) est positionnée dans l’espace 3D. Elle vise un endroit
de l’espace par son axe Z (c’est la direction du regard ou Look Direction). Elle peut
aussi pivoter sur elle-même d’un angle dans le plan XY (c’est la direction du haut ou
Up Direction). Avec un plan de projection perpendiculaire à l’axe Z de la caméra, si la
direction du haut tourne, les points projetés sur le plan de projection seront à des
emplacements différents. Le plan de projection est caractérisé par sa largeur et sa
hauteur, et par la distance qui le sépare de la caméra.
L’UserControl ProjectionParallele.xaml, dans le dossier chapitre_02, permet de mettre
en œuvre l’utilisation d’une projection parallele en fixant les différents paramètres de
la caméra pour déterminer le rendu en 2D (figure 2.15).
Différents contrôles de type Slider permettent de régler, dans des plages de valeurs,
la position de la caméra dans l’espace 3D, la direction du regard de la caméra et
l’inclinaison (direction du haut) de la caméra. Le cube possède six faces de couleurs
différentes. Il y a six boutons qui permettent d’effectuer un réglage prédéfini de façon
à voir le cube de face, de droite, de gauche, de dessus, de dessous et par l’arrière. Un
dernier bouton permet de revenir à la position initiale. Les axes sont repérés par des
couleurs: rouge pour l’axe des X, bleu pour l’axe des Y et vert pour l’axe des Z.
D’un point de vue mathématique (figure 2.14), on a xp=d, yp=d et zp=d.
FIGURE 2.14 Y
X
Pp(xp,yp,zp) P(x,y,z)
X Y
Pp(xp,yp,zp) P(x,y,z) Pp(xp,yp,zp) P(x,y,z)
Z Z
d d
Copyright 2013 Patrice REY
A chaque changement d’une position sur les glissières de type Slider, la méthode
FixerLaMatriceDeLaCamera est appelée pour recalculer la matrice Matrix3D de
transformation de l’affichage (propriété ViewMatrix) et pour recalculer la matrice
Matrix3D de transformation de la projection (propriété ProjectionMatrix).
private void FixerLaMatriceDeLaCamera() {
x_camera_matrice.ViewMatrix = FixerMatriceVue(
m_pos_camera, m_look_direction, m_up_direction);
x_camera_matrice.ProjectionMatrix = FixerParametreParallele(
m_largeur, m_largeur, m_plan_pres, m_plan_loin);
}
Pour calculer la matrice de transformation de l’affichage, on passe les paramètres m_
pos_camera, m_look_direction et m_up_direction à la méthode FixerMatriceVue. Pour
calculer la matrice de transformation de la projection, on passe les paramètres m_
largeur, m_plan_pres et m_plan_loin à la méthode FixerParametreParallele.
Dans la projection parallèle, le plan de projection a des dimensions qui sont fonction
de la largeur et de la hauteur que l’on fixe. Ici on définit la largeur a une valeur de 10 et
la hauteur a une valeur de 5. On peut implémenter la méthode FixerParametreParallele
pour trouver la matrice de transformation de projection.
public static Matrix3D FixerParametreParallele(double largeur,
double hauteur, double plan_pres, double plan_loin) {
Matrix3D matrice_parallele = new Matrix3D();
matrice_parallele.M11 = 2 / largeur;
matrice_parallele.M22 = 2 / hauteur;
Copyright 2013 Patrice REY
Au sommaire de ce chapitre
• définition des classes de transformations 3D
• utilisation pratique de la transformation ScaleTransform3D
• utilisation pratique de la transformation TranslateTransform3D
• utilisation pratique de la transformation RotateTransform3D
• utilisation pratique de la transformation MatrixTransform3D
• principe des combinaisons de transformations
90 Programmez des jeux vidéo 3D avec C#5 et WPF
Le principe des transformations 3D est identique à celui des transformations 2D. Il
consiste en l’application d’un système de coordonnées transformées mappé sur le
système de coordonnées initial en fonction d’une matrice. Il y a des transformations de
haut niveau et des transformations par application directe d’une matrice. Nous allons
voir dans ce chapitre les classes de haut niveau que WPF fournit pour une mise en
œuvre plus simple.
Les classes de transformations 3D de haut niveau qui sont fournies par WPF héritent
de la classe abstraite Transform3D. La classe Transform3D se trouve dans l’espace
de noms System.Windows.Media.Media3D (assembly PresentationCore.dll). Comme le
visualise l’arbre d’héritage de cette classe (figure 3.1), ses classes dérivées sont:
• AffineTransform3D est une classe abstraite qui sert de classe de base de laquelle
dérivent toutes les transformations affines concrètes 3D (translation, rotation et
mise à l’échelle).
• MatrixTransform3D est une classe qui crée une transformation spécifiée par
une structure Matrix3D, utilisée pour manipuler des objets ou des systèmes de
coordonnées dans l’espace universel 3D.
• Transform3DGroup est une classe qui représente une transformation qui
correspond à un élément composite des enfants Transform3D de la collection
Transform3DCollection.
Les classes dérivées de la classe AffineTransform3D représentent les transformations
de haut niveau. Il y a:
• ScaleTransform3D est une transformation affine qui met à l’échelle (agrandissement
ou réduction) un objet dans le plan (XYZ) tridimensionnel, à partir d’un point
central défini; les facteurs d’échelle sont définis sur les axes X, Y et Z à partir d’un
point central.
• TranslateTransform3D est une transformation affine qui déplace un objet dans le
plan (XYZ) tridimensionnel.
Copyright 2013 Patrice REY
2 - La transformation ScaleTransform3D
d’un point central défini. Les facteurs d’échelle sont définis sur les axes X, Y et Z à partir
d’un point central. Elle expose principalement les propriétés suivantes:
• CenterX qui définit la coordonnée x du point central de la transformation.
• CenterY qui définit la coordonnée y du point central de la transformation.
• CenterZ qui définit la coordonnée z du point central de la transformation.
• ScaleX qui définit le facteur d’échelle sur l’axe X.
• ScaleY qui définit le facteur d’échelle sur l’axe Y.
• ScaleZ qui définit le facteur d’échelle sur l’axe Z.
• Value qui obtient une représentation Matrix3D de cette transformation.
• IsAffine qui obtient une valeur qui indique si la transformation est affine ou pas.
• Inverse qui obtient la transformation inverse de cet objet si elle existe.
92 Programmez des jeux vidéo 3D avec C#5 et WPF
Le constructeur par défaut de cette classe est ScaleTransform3D(). Plusieurs
constructeurs avec paramètres sont disponibles avec notamment:
• ScaleTransform3D(Vector3D) qui initialise une nouvelle instance de la classe
ScaleTransform3D à l’aide du Vector3D spécifié.
• ScaleTransform3D(Vector3D, Point3D) qui initialise une nouvelle instance de la
classe ScaleTransform3D avec les Vector3D et Point3D spécifiés.
• ScaleTransform3D(Double, Double, Double) qui initialise une nouvelle instance de la
classe ScaleTransform3D à l’aide des facteurs d’échelle spécifiés.
• ScaleTransform3D(Double, Double, Double, Double, Double, Double) qui initialise
une nouvelle instance de la classe ScaleTransform3D à l’aide des coordonnées de
centre spécifiées et des facteurs d’échelle.
L’UserControl ScaleTransform3d.xaml (figures 3.2 et 3.3), dans le dossier chapitre_03,
permet de visualiser un cube coloré (avec six faces colorées différentes) pour lequel on
peut faire varier la mise à l’échelle suivant X, suivant Y et suivant Z. Les deux modes de
projection pour la visualisation du cube en 3D sont accessibles par des RadioButton
à cocher (projection perspective et projection parallèle). Un conteneur de type Grid
affiche les composantes de la matrice de transformation ScaleTransform3D en cours. On
ajoute à la définition du cube coloré en XAML, un groupe, de type Transform3DGroup,
contenant les transformations 3D à effectuer. Une transformation de mise à l’échelle
x_cub_echelle de type ScaleTransform3D est ajoutée, en lui fixant le centre de la
transformation à la coordonnée CenterX=0, CenterY=0 et CenterZ=0.
<Transform3DGroup>
<TranslateTransform3D OffsetZ=’1’></TranslateTransform3D>
<ScaleTransform3D
x:Name=’x_cube_echelle’
CenterX=’0’ CenterY=’0’
CenterZ=’0’></ScaleTransform3D>
</Transform3DGroup>
Lors du déplacement d’une glissière de type Slider, on appelle la méthode
TransformerCube. Elle permet de modifier les propriétés ScaleX, ScaleY et ScaleZ de la
transformation en fonction des valeurs relevées sur les glissières.
Copyright 2013 Patrice REY
3 - La transformation TranslateTransform3D
x:Name=’x_cube_trans’
OffsetX=’0’ OffsetY=’0’
OffsetZ=’0’>
</TranslateTransform3D>
</Transform3DGroup>
Lors du déplacement d’une glissière de type Slider, on appelle la méthode
TransformerCube. Elle permet de modifier les propriétés OffsetX, OffsetY et OffsetZ de
la transformation en fonction des valeurs relevées sur les glissières.
CHAPITRE 3 Les transformations 3D 97
FIGURE 3.4
4 - La transformation RotateTransform3D
5 - La transformation MatrixTransform3D
permet de visualiser un cube pour lequel on peut modifier les valeurs de la matrice de
transformation par l’intermédiaire des glissières. Il y a 16 glissières qui correspondent
aux 16 composantes de la matrice de transformation. Trois boutons supplémentaires
sont ajoutés avec un réglage prédéfini concernant les inclinaisons. Les deux modes de
projection pour la visualisation du cube en 3D sont accessibles par des RadioButton
à cocher (projection perspective et projection parallèle). Un conteneur de type Grid
affiche les composantes de la matrice de transformation MatrixTransform3D en cours.
CHAPITRE 3 Les transformations 3D 105
FIGURE 3.9
Il est ainsi possible d’effectuer sur le cube toutes les transformations que l’on désire
seulement en agissant sur les glissières.
On ajoute à la définition du cube coloré, en XAML, un groupe, de type Transform3DGroup,
contenant les transformations 3D à effectuer. Une transformation par matrice x_cube_
matrice de type MatrixTransform3D est ajoutée. Pour initialiser la propriété Matrix de
la matrice de transformation, on ajoute un nœud <MatrixTransform3D.Matrix> et on
définit les 16 composantes de l’objet Matrix3D (propriétés M11, M12, M13, M14, M21,
M22, M23, M24, M31, M32, M33, M34, OffsetX, OffsetY, OffsetZ et M44).
<Transform3DGroup>
<MatrixTransform3D
x:Name=’x_cube_matrice’>
<MatrixTransform3D.Matrix>
<Matrix3D M11=’1’ M12=’0’
M13=’0’ M14=’0’ M21=’0’
M22=’1’ M23=’0’ M24=’0’
M31=’0’ M32=’0’ M33=’1’
M34=’0’ OffsetX=’0’
OffsetY=’0’ OffsetZ=’0’
M44=’1’></Matrix3D>
</MatrixTransform3D.Matrix>
</MatrixTransform3D>
</Transform3DGroup>
Dès que l’on modifie la position d’une glissière de type Slider, on affiche la valeur au format
texte au-dessus de la glissière et on appelle la méthode FixerMatriceTransformation.
//changement de valeur des slider
private void x_slider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e) {
Slider recup = (Slider)sender;
switch (recup.Name) {
case «x_slider_m11»:
x_text_m11.Text = «M11 = « +
Math.Round(x_slider_m11.Value, 2).ToString();
break;
case «x_slider_m12»:
x_text_m12.Text = «M12 = « +
Math.Round(x_slider_m12.Value, 2).ToString();
break;
...
}
FixerMatriceTransformation();
}
La méthode FixerMatriceTransformation permet de modifier la matrice de la
transformation x_cube_matrice. Pour cela, on instancie une nouvelle matrice matrice de
type Matrix3D. On affecte à ses 16 composantes la valeur correspondante en fonction
108 Programmez des jeux vidéo 3D avec C#5 et WPF
de la propriété Value des glissières. Puis on affecte cette matrice à la propriété Matrix
de la matrice de transformation x_cube_matrice.
//definir la matrice de la transformation
private void FixerMatriceTransformation() {
...
Matrix3D matrice = new Matrix3D();
matrice.M11 = x_slider_m11.Value;
matrice.M12 = x_slider_m12.Value;
matrice.M13 = x_slider_m13.Value;
matrice.M14 = x_slider_m14.Value;
matrice.M21 = x_slider_m21.Value;
matrice.M22 = x_slider_m22.Value;
matrice.M23 = x_slider_m23.Value;
matrice.M24 = x_slider_m24.Value;
matrice.M31 = x_slider_m31.Value;
matrice.M32 = x_slider_m32.Value;
matrice.M33 = x_slider_m33.Value;
matrice.M34 = x_slider_m34.Value;
matrice.OffsetX = x_slider_m41.Value;
matrice.OffsetY = x_slider_m42.Value;
matrice.OffsetZ = x_slider_m43.Value;
matrice.M44 = x_slider_m44.Value;
x_cube_matrice.Matrix = matrice;
AfficherMatriceTransform();
...
}
Nous avons vu que WPF ne fournissait pas de transformation de haut niveau pour
exécuter une inclinaison en 3D. Pour cela, il faut agir directement sur la matrice. Il y
a trois boutons x_btn_incliner_x, x_btn_incliner_y et x_btn_incliner_z qui possèdent un
réglage prédéfini pour visualiser une inclinaison.
Par exemple, le bouton x_btn_incliner_x visualise une inclinaison du cube suivant l’axe
X (avec une valeur d’inclinaison égale à 2). Le bouton x_btn_incliner_y visualise une
inclinaison du cube suivant l’axe Y (avec une valeur d’inclinaison égale à -1). Le bouton
x_btn_incliner_z visualise une inclinaison du cube suivant l’axe Y (avec une valeur
d’inclinaison égale à 2).
//reglage predefini : inclinaison suivant x
Copyright 2013 Patrice REY
RemiseAZero();
x_slider_m32.Value = -1;
FixerMatriceTransformation();
}
//reglage predefini : inclinaison suivant z
private void x_btn_incliner_z_Click(object sender,
RoutedEventArgs e) {
RemiseAZero();
x_slider_m23.Value = 2;
FixerMatriceTransformation();
}
6 - Combinaison de transformations 3D
Au sommaire de ce chapitre
• organisation d’une scène 3D
• visualisation d’une scène 3D avec le contrôle Viewport3D
• les maillages et la géométrie 3D
• les conteneurs recevant des objets 3D
• les différentes sources de lumière
• les différents types de caméra
112 Programmez des jeux vidéo 3D avec C#5 et WPF
La scène 3D est un objet complexe. C’est l’endroit où l’on positionne des éléments en
3D dans le but d’effectuer un rendu en 2D. Ce sont ces éléments qui permettent de
donner un réalisme à la scène 3D.
Dans ce chapitre nous allons voir comment s’organise une scène 3D au travers des
éléments nécessaires à sa composition avec notamment le Viewport3D, les maillages
et la géométrie 3D de base, les conteneurs qui reçoivent les objets 3D, les différentes
sources de lumière, les différentes caméras et leurs paramètres.
1 - Visualisation de la scène
La création d’une scène 3D avec l’objet le plus simple qu’est le triangle, ne peut se faire
qu’au travers d’un ensemble d’étapes qui sont:
• l’ajout d’un contrôle Viewport3D qui restitue le contenu 3D dans les limites de
disposition 2D de l’élément Viewport3D.
• l’ajout des objets 3D qui dérivent généralement des objets ModelVisual3D ou
ModelUIElement3D.
• l’ajout d’une source de lumière qui illumine la scène.
• l’application de textures à l’objet 3D qui déterminent l’apparence de sa surface.
• le positionnement de caméra, qui projette l’objet 3D dans une représentation 2D,
et à partir de laquelle on peut visualiser la scène.
Cette liste d’étapes est juste une liste basique mais incontournable pour réaliser une
scène 3D.
Une scène 3D peut être très complexe en pratique, en contenant de nombreux objets
3D, plusieurs sources d’illumination, de nombreuses caméras pour avoir différents
points de visualisation, ainsi que différents matériaux pour la composition des objets.
Tout cet ensemble concoure au réalisme d’une scène 3D mais oblige le programmeur
à effectuer la même démarche quel que soit l’objet 3D à visualiser.
Le contenu graphique 3D dans WPF est encapsulé dans un élément Viewport3D qui
peut participer à la structure du document à deux dimensions. Le système graphique
traite Viewport3D comme un élément visuel à deux dimensions semblable à bien
d’autres éléments dans WPF. Le Viewport3D fonctionne comme une fenêtre d’affichage
dans une scène tridimensionnelle. Plus précisément, c’est une surface sur laquelle une
scène 3D est projetée.
CHAPITRE 4 La scène 3D 113
En pratique, un Viewport3D sera ajouté dans une fenêtre de type Window ou dans
un contrôle de type UserControl. Généralement on l’encadre dans un contrôle Border
de façon à identifier la zone 3D. Ce Viewport3D doit avoir ses propriétés Width et
Height explicitement définies. De plus, sa propriété héritée ClipToBounds sera fixée
à true (la propriété UIElement.ClipToBounds définit une valeur qui indique s’il faut
découper le contenu de cet élément, ou le contenu qui provient d’éléments enfants
de cet élément, pour l’ajuster aux dimensions de l’élément contenant; il s’agit d’une
propriété de dépendance).
La structure XAML sera principalement de la forme suivante:
114 Programmez des jeux vidéo 3D avec C#5 et WPF
<Border BorderBrush=’Black’ BorderThickness=’1’ Width=’400’
Height=’400’ Canvas.Left=’12’ Canvas.Top=’68’
Background=’Black’>
<!-- le controle viewport3d -->
<Viewport3D ClipToBounds=’True’ Width=’400’ Height=’400’>
<!-- les ressources du viewport3d -->
<Viewport3D.Resources>
...
</Viewport3D.Resources>
<!-- les caméras du viewport3d -->
<Viewport3D.Camera>
...
</Viewport3D.Camera>
<!-- le conteneur des objets 3D du viewport3d -->
<ContainerUIElement3D>
...
</ContainerUIElement3D>
</Viewport3D>
</Border>
3 - La création de modèles 3D
La classe ModelVisual3D, issue du framework .NET 3.0, est une classe bivalente (figure
4.2). Elle permet d’afficher un modèle défini par un Model3D (GeometryModel3D ou
Model3DGroup) spécifié dans la propriété Content. Elle peut contenir d’autres objets
Visual3D spécifiés dans la propriété Children.
Les modèles composites peuvent être créés au moyen de la propriété Children, mais
également en définissant un contenu de type Model3DGroup dans la propriété Content
qui permet de regrouper plusieurs Model3D.
L’interactivité sur ModelVisual3D peut être obtenue au moyen du test d’atteinte (hit
Copyright 2013 Patrice REY
testing). Le test d’atteinte est une technique de bas niveau que nous ne détaillerons pas
car elle est avantageusement remplacée par les fonctionnalités de UIElement3D. Voici
un exemple de code représentant un modèle ModelVisual3D composite regroupant
plusieurs GeometryModel3D:
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
CHAPITRE 4 La scène 3D 115
<GeometryModel3D ...></GeometryModel3D>
<GeometryModel3D ...></GeometryModel3D>
<GeometryModel3D ...></GeometryModel3D>
<GeometryModel3D ...></GeometryModel3D>
...
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
FIGURE 4.2
La classe abstraite UIElement3D est apparue dans le framework .NET 3.5, accompagnée
de deux classe héritées (figure 4.3): ModelUIElement3D et ContainerUIElement3D.
Ces classes offrent une alternative intéressante à ModelVisual3D car elles sont plus
évoluées sur le plan fonctionnel et aussi car chacune est spécialisée:
• UIElement3D hérite de Visual3D et apporte une gestion de haut niveau de
l’interactivité (gestion du focus, support du clavier, du stylet, du toucher et de la
souris); son interface est proche de celle de UIElement.
• ModelUIElement3D permet d’afficher un modèle défini par un Model3D
(GeometryModel3D ou Model3DGroup) spécifié dans la propriété Model.
• ContainerUIElement3D peut contenir d’autres objets Visual3D spécifiés dans la
propriété Children.
FIGURE 4.3
Il existe toujours deux techniques pour créer des modèles composites, mais
ici en exploitant deux classes différentes: assembler des Visual3D au sein d’un
ContainerUIElement3D; assembler des Model3D au sein d’un Model3DGroup assigné
CHAPITRE 4 La scène 3D 117
La classe abstraite Model3D représente une description d’objet 3D. Un objet Model3D
peut être utilisé pour définir le visuel d’un objet Visual3D. Comme le montre l’arbre
d’héritage de la figure 4.4, les classes concrètes qui dérivent de Model3D sont:
• GeometryModel3D qui définit un modèle basé sur une géométrie 3D.
• Model3DGroup qui permet l’utilisation de plusieurs modèles Model3D en tant
qu’unité.
Un objet GeometryModel3D définit un modèle basé sur une géométrie 3D. Il sert à
réaliser un modèle 3D au moyen de ses propriétés:
• Geometry qui représente une géométrie 3D.
• Material qui représente une matière pour la face avant spécifiée.
• BackMaterial qui représente une matière pour la face arrière spécifiée (propriété
facultative).
118 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 4.4
4 - La géométrie Geometry3D
La classe Geometry3D est une classe abstraite qui sert de classe de base pour la
géométrie 3D. Les classes qui dérivent de cette classe de base abstraite définissent des
formes géométriques 3D. La classe d’objets Geometry3D peut être utilisée pour le test
d’atteinte et pour le rendu des données graphiques 3D.
Copyright 2013 Patrice REY
Une géométrie 3D contient les données d’un modèle mais ne sait pas s’afficher
elle-même. Le seul type de géométrie hérité de la classe abstraite Geometry3D est
MeshGeometry3D. La figure 4.5 visualise l’arbre d’héritage de la classe Geometry3D.
La classe MeshGeometry3D définit le maillage d’un modèle 3D au moyen des propriétés
suivantes: la propriété Positions représente une collection des points formant les
sommets (vertex) du maillage (du type Point3D); la propriété TriangleIndices représente
une collection des indices des positions formant les triangles des faces du maillage (du
CHAPITRE 4 La scène 3D 119
type Int32); la propriété Normals représente une collection des vecteurs normaux qui
sont utilisés pour le calcul de l’angle de réflexion des rayons lumineux sur la surface; la
propriété TextureCoordinates représente une collection des points 2D de la texture (le
contenu dans le pinceau) correspondant aux positions des sommets du maillage.
FIGURE 4.5
Pour que la production d’un contenu soit visible, la géométrie doit au moins
définir les propriétés Positions et TriangleIndices. Une valeur par défaut est calculée
automatiquement pour la propriété Normals. L’initialisation de TextureCoordinates
n’est pas nécessaire tant qu’une matière sans texture est utilisée.
La base des maillages est le triangle. Un modèle 3D est un assemblage de facettes
triangulaires. Le triangle est la surface la plus simple qui puisse exister. En effet, trois
points suffisent pour définir un triangle.
Les triangles du modèle sont définis au moyen des points formant les sommets du
maillage. La propriété TriangleIndices contient des séries de trois valeurs correspondant
aux indices des sommets (qui sont indiqués dans la propriété Positions) formant chacune
un triangle. L’ordre dans lequel les indices d’un triangle sont spécifiés détermine le
côté, avant ou arrière, sur lequel la face est représentée par rapport à la position du
120 Programmez des jeux vidéo 3D avec C#5 et WPF
point de vue courant. Pour être face à l’observateur, les indices doivent être spécifiés
dans l’ordre inverse des aiguilles d’une montre.
La figure 4.6 visualise la réalisation d’un modèle MeshGeometry3D constitué d’un
simple triangle. Le triangle est composé des trois points A(0,3,0), B(4,0,0) et C(0,0,3).
On affecte à chaque point un numéro d’indice, avec par exemple 0 pour A, 1 pour B
et 2 pour C. Quand on observe le triangle, on veut que la face de devant soit celle qui
est vue (face qui comporterait une couleur ou une texture par exemple). La démarche
consiste à exprimer les points dans l’ordre des indices en écrivant:
Positions = «0 3 0, 4 0 0, 0 0 3»
(la notation suivante est acceptée aussi: Positions = «0,3,0 4,0,0 0,0,3»)
Puis il faut exprimer l’ordre des indices par:
TriangleIndices = «0 2 1»
(la notation suivante est acceptée aussi: TriangleIndices = «0,2,1»)
L’ordre «0 2 1» est dans le sens antihoraire, donc la face du triangle que nous voyons,
sera la face qui comportera la couleur ou la texture. La face arrière de ce triangle
comportera une couleur ou une texture différente.
Y
FIGURE 4.6
sens
antihoraire
indice 0
A(0,3,0)
indice 1
B(4,0,0)
X
Copyright 2013 Patrice REY
indice 2
C(0,0,3)
Z
CHAPITRE 4 La scène 3D 121
sens
antihoraire
indice 0
A(4,2,0)
indice 1
indice 3
D(0,2,3) B(4,0,0)
X
indice 2
C(0,0,3)
<ModelVisual3D>
Copyright 2013 Patrice REY
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions=’4 2 0, 4 0 0, 0 0 3, 0 2 3’
TriangleIndices=’2 1 0, 0 3 2’></MeshGeometry3D>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial Brush=’Red’></DiffuseMaterial>
CHAPITRE 4 La scène 3D 123
</GeometryModel3D.Material>
<GeometryModel3D.BackMaterial>
<DiffuseMaterial Brush=’Yellow’></DiffuseMaterial>
</GeometryModel3D.BackMaterial>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
5 - Matière et surface
Une matière est un objet hérité de la classe Material qui définit l’apparence d’une
surface, au moyen d’un pinceau spécifié dans sa propriété Brush (propriété incluant
tous les pinceaux 2D). Les pinceaux évolués contenant une image ou un objet Visual
doivent être appliqués à des modèles gérant le placage de texture. La figure 4.8 visualise
l’arbre d’héritage de la classe de base abstraite Material.
FIGURE 4.8
124 Programmez des jeux vidéo 3D avec C#5 et WPF
Les classes dérivées de la classe Material sont:
• DiffuseMaterial qui représente une surface autorisant l’application d’un pinceau
2D, tel qu’un SolidColorBrush ou un TileBrush, à un modèle 3D éclairé de manière
diffuse.
• EmissiveMaterial qui représente une surface autorisant l’application d’un Brush
à un modèle 3D afin qu’il soit compris dans les calculs de l’éclairage comme si les
surfaces émettaient une lumière égale à la couleur du Brush.
• SpecularMaterial qui représente une surface autorisant l’application d’un pinceau
2D, tel qu’un SolidColorBrush ou un TileBrush, à un modèle 3D éclairé de manière
spéculaire.
• MaterialGroup qui représente un Material qui forme un élément composite de la
collection des matières.
Une surface mate est définie par un objet DiffuseMaterial. La caractéristique d’une
surface mate est de diffuser la lumière qu’elle reçoit et dont le rendu est fonction de
l’éclairage. Les parties à l’ombre sont noires.
La classe DiffuseMaterial expose principalement les propriétés suivantes:
• Brush qui représente le pinceau utilisé.
• Color qui définit la couleur réfléchie pour la texture de la matière avec un éclairage
normal.
• AmbientColor qui définit la couleur réfléchie pour la texture de la matière avec un
éclairage de type AmbientLight.
Par exemple, la face d’un rectangle peinte en rouge s’exprimera en XAML par:
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions=’4 2 0, 4 0 0, 0 0 3, 0 2 3’
TriangleIndices=’2 1 0, 0 3 2’></MeshGeometry3D>
</GeometryModel3D.Geometry>
Copyright 2013 Patrice REY
<GeometryModel3D.Material>
<DiffuseMaterial Brush=’Red’></DiffuseMaterial>
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
CHAPITRE 4 La scène 3D 125
Une surface lumineuse est définie par un objet EmissiveMaterial. Cette surface
lumineuse est indépendante de l’éclairage ambiant. Mais un objet avec une surface
lumineuse n’illumine pas les éléments 3D présents dans son entourage immédiat.
La classe EmissiveMaterial expose principalement les propriétés suivantes:
• Brush qui représente le pinceau utilisé.
• Color qui définit la couleur réfléchie pour la texture de la matière.
Par exemple, la face d’un rectangle peinte en jaune s’exprimera en XAML par:
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions=’4 2 0, 4 0 0, 0 0 3, 0 2 3’
TriangleIndices=’2 1 0, 0 3 2’></MeshGeometry3D>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<EmissiveMaterial Brush=’Yellow’></EmissiveMaterial >
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
Les matières peuvent être combinées au sein d’un objet MaterialGroup, formant
alors une matière composite. La propriété Children de MaterialGroup contient une
collection d’objets Material enfants qui constituent la matière composite. Par exemple,
un MaterialGroup permet de définir une surface unie ornée de motifs lumineux
visibles même dans l’ombre. Un objet DiffuseMaterial définit la surface unie. Un objet
DrawingBrush, utilisé par un objet EmissiveMaterial, définit les motifs lumineux. Cela
s’exprimera en XAML par:
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions=’4 2 0, 4 0 0, 0 0 3, 0 2 3’
TriangleIndices=’2 1 0, 0 3 2’></MeshGeometry3D>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<MaterialGroup>
126 Programmez des jeux vidéo 3D avec C#5 et WPF
<DiffuseMaterial Brush=’Yellow’></DiffuseMaterial >
<EmissiveMaterial>
<EmissiveMaterial.Brush>
<DrawingBrush>
<DrawingBrush.Drawing>
<GeometryDrawing Brush=’MediumBlue’>
...
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</EmissiveMaterial.Brush>
</EmissiveMaterial>
</MaterialGroup>
</GeometryModel3D.Material>
</GeometryModel3D>
</ModelVisual3D.Content>
</ModelVisual3D>
TriangleIndices=’2 1 0, 0 3 2’></MeshGeometry3D>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<MaterialGroup>
<DiffuseMaterial Brush=’Yellow’></DiffuseMaterial >
<SpecularMaterial SpecularPower=’100.0’ Brush=’White’/>
</MaterialGroup>
</GeometryModel3D.Material>
</GeometryModel3D>
CHAPITRE 4 La scène 3D 127
</ModelVisual3D.Content>
</ModelVisual3D>
6 - L’éclairage de la scène
L’éclairage est géré par des objets dérivés de la classe de base abstraite Light. La figure
4.9 visualise l’arbre d’héritage de la classe Light. WPF gère l’éclairage par un objet
Model3D d’un type particulier, invisible, utilisé pour calculer les couleurs des éléments
de la scène. C’est pour cette raison que l’objet Light hérite de la classe Model3D.
FIGURE 4.9
7 - Les caméras
NearPlaneDistance
UpDirection
Y
LookDirection
Z
X FieldOfView
centre de
projection
(caméra)
plan de
projection volume
d’observation
plan de loin
FIGURE 4.12
FarPlaneDistance
NearPlaneDistance
plan de
projection
UpDirection Width
Y
LookDirection
Z
X
centre de
projection à
l’infini (caméra)
plan de volume
près d’observation plan de
loin
5
Les modèles 3D basiques
Au sommaire de ce chapitre
• la modélisation de la facette triangulaire et de la facette rectangulaire avec
des faces colorées et texturées
• la modélisation du cube, du plan de travail et des axes de coordonnées
• la définition et l’application des coordonnées sphériques
• la modélisation du cylindre et la géométrie polygonale
• la modélisation du cône
• la modélisation de la sphère et de l’ellipsoïde
• la modélisation du tore
134 Programmez des jeux vidéo 3D avec C#5 et WPF
Les modèles 3D sont composés par des assemblages dont l’assemblage de base est
le triangle. Deux triangles qui sont joints par leur hypoténuse forment une facette
rectangulaire. Nous allons voir dans ce chapitre comment réaliser des assemblages
basiques et comment les visualiser. A partir de ces assemblages, nous verrons la
réalisation des principaux modèles 3D basiques que sont le cube, la sphère, le cylindre,
le cône et le tore.
1 - La modélisation basique
...
</Viewport3D>
</Border>
Pour que la zone de rendu affiche quelque chose, il faut ajouter une caméra (propriété
Camera du Viewport3D). Pour cela on ajoute une caméra x_camera à projection
perspective, de type PerspectiveCamera. On fixe sa position dans l’espace 3D (Position
= «0,0,5»), son axe de visée est représenté par un vecteur (LookDirection = «0,0,-5»), sa
CHAPITRE 5 Les modèles 3D basiques 135
FIGURE 5.1
direction vers le haut est représentée par un vecteur (UpDirection = «0 1 0») et l’angle
de vue est fixé à 60 (FieldOfView = «60»). Pour que la caméra fixe en permanence
l’origine du repère 3D, il suffit de fixer l’orientation de la visée par le vecteur qui va du
point (0,0,5) qui correspond à la position de la caméra, au point (0,0,0) qui est l’origine.
Cela se traduit donc par un Vector3D(0,0,-5) pour une position de caméra de (0,0,5) soit
le signe opposé aux trois coordonnées de la caméra.
Le UpDirection de la caméra est celui de l’axe Y pour signifier aucune inclinaison de la
caméra. Son inclinaison est donc représentée par le Vector3D(0,1,0). Le plan de près de
projection (propriété NearPlaneDistance) est fixé à sa valeur par défaut (0.125). Le plan
de loin (propriété FarPlaneDistance) est fixé à une valeur de 100.
<!-- zone de rendu 3d -->
<Viewport3D x:Name=ˈx_viewportˈ ClipToBounds=ˈTrueˈ Width=ˈ791ˈ Height=ˈ422ˈ>
<!-- cameras -->
<Viewport3D.Camera>
<PerspectiveCamera x:Name=ˈx_cameraˈ Position=ˈ0,0,5ˈ LookDirection=ˈ0,0,-5ˈ
FieldOfView=ˈ60ˈ UpDirection=ˈ0 1 0ˈ NearPlaneDistance=ˈ0.125ˈ
136 Programmez des jeux vidéo 3D avec C#5 et WPF
FarPlaneDistance=ˈ100ˈ></PerspectiveCamera>
</Viewport3D.Camera>
</Viewport3D>
Les objets 3D sont positionnés dans un objet ContainerUIElement3D qui est affecté
à la propriété Children de Viewport3D (propriété implicite en XAML donc elle n’a
pas besoin d’être mentionnée avec <Viewport3D.Children>). On choisit un objet
ModelUIElement3D pour restituer un modèle 3D qui prend en charge l’entrée, le focus
et les événements.
<!-- zone de rendu 3d -->
<Viewport3D x:Name=ˈx_viewportˈ ClipToBounds=ˈTrueˈ Width=ˈ791ˈ Height=ˈ422ˈ>
...
<!-- conteneur des modeles **************** -->
<ContainerUIElement3D>
<ModelUIElement3D>
...
</ModelUIElement3D>
<ModelUIElement3D>
...
</ModelUIElement3D>
...
</ContainerUIElement3D>
...
</Viewport3D>
La propriété implicite Model de ModelUIElement3D (<ModelUIElement3D.Model>) est
constituée des objets x_groupe_triangle_hd, x_groupe_triangle_hg, x_groupe_triangle_bd
et x_groupe_triangle_bg, de type Model3DGroup. Ces objets représentent les différents
maillages qui sont apportés à la scène 3D.
<!-- zone de rendu 3d -->
<Viewport3D x:Name=ˈx_viewportˈ ClipToBounds=ˈTrueˈ Width=ˈ791ˈ Height=ˈ422ˈ>
...
<!-- conteneur des modeles **************** -->
<ContainerUIElement3D>
<!-- triangle haut droit -->
<ModelUIElement3D x:Name=ˈx_modele_triangle_hdˈ>
<Model3DGroup x:Name=ˈx_groupe_triangle_hdˈ>
...
Copyright 2013 Patrice REY
</Model3DGroup>
</ModelUIElement3D>
<!-- triangle haut gauche -->
<ModelUIElement3D x:Name=ˈx_modele_triangle_hgˈ>
<Model3DGroup x:Name=ˈx_groupe_triangle_hgˈ>
...
</Model3DGroup>
</ModelUIElement3D>
<!-- triangle bas droit -->
CHAPITRE 5 Les modèles 3D basiques 137
<ModelUIElement3D x:Name=ˈx_modele_triangle_bdˈ>
<Model3DGroup x:Name=ˈx_groupe_triangle_bdˈ>
...
</Model3DGroup>
</ModelUIElement3D>
<!-- triangle bas gauche -->
<ModelUIElement3D x:Name=ˈx_modele_triangle_bgˈ>
<Model3DGroup x:Name=ˈx_groupe_triangle_bgˈ>
...
</Model3DGroup>
</ModelUIElement3D>
...
</ContainerUIElement3D>
...
</Viewport3D>
Un éclairage x_lumiere, de type AmbientLight, permet d’éclairer la scène. On fixe la
propriété Color de AmbientLight à White pour un éclairage blanc.
<!-- zone de rendu 3d -->
<Viewport3D x:Name=ˈx_viewportˈ ClipToBounds=ˈTrueˈ Width=ˈ791ˈ Height=ˈ422ˈ>
...
<ContainerUIElement3D>
<!-- lumiere -->
<ModelUIElement3D>
<Model3DGroup x:Name=ˈx_lumiereˈ>
<AmbientLight Color=ˈWhiteˈ></AmbientLight>
</Model3DGroup>
</ModelUIElement3D>
...
</ContainerUIElement3D>
...
</Viewport3D>
Les quatre triangles sont positionnés à l’origine lors de leur construction. Puis,
avec des transformations 3D, ils sont translatés (avec une transformation de type
TranslateTransform3D) et éventuellement, ils subissent une rotation (avec une
transformation de type RotateTransform3D). La figure 5.2 visualise la réalisation du
triangle avec 3 points et 3 indices lors de sa construction, et la figure 5.3 visualise la
position de la caméra vis-à-vis du triangle.
Le triangle en haut à droite est le premier triangle positionné (x_groupe_triangle_hd
de type Model3DGroup). Un objet GeometryModel3D, intitulé x_geo_triangle_hd,
constitue le modèle 3D du triangle. La propriété Geometry de GeometryModel3D
(<GeometryModel3D.Geometry>) contient la définition du maillage du triangle. Ce
maillage est un MeshGeometry3D avec sa propriété Positions fixée à «0 0 0, 1 0 0, 0 1
0», et sa propriété TriangleIndices fixée à «0 1 2».
138 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 5.2
Y
(0,2,0) indice 2
angle 30°
(0,0,5) caméra
Z
Y
FIGURE 5.3
FarPlaneDistance
= infini par défaut
Copyright 2013 Patrice REY
X
LookDirection
(0,0,-5)
(0,0,5) caméra
Z
CHAPITRE 5 Les modèles 3D basiques 139
</LinearGradientBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
Il en est de même si le pinceau peint, avec non pas un dégradé de couleurs, mais avec
une image de référence de type ImageBrush (la propriété ImageSource pointe vers la
ressource image servant pour la peinture à effectuer). Cela donnerait par exemple ici:
CHAPITRE 5 Les modèles 3D basiques 141
<GeometryModel3D x:Name=ˈx_geo_triangle_hdˈ>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions=ˈ0 0 0, 1 0 0, 0 1 0ˈ TriangleIndices=ˈ0 1 2ˈ
TextureCoordinates=ˈ0,1 1,1 0,0ˈ></MeshGeometry3D>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial x:Name=ˈx_surface_triangle_hdˈ>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource=ˈimage_texture/texture_triangle_1.pngˈ />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
La propriété TextureCoordinates du MeshGeometry3D permet de mapper un placage
de texture sur une surface. Ce placage de texture ne peut être réalisé que si la propriété
TextureCoordinates est explicitement exprimée. La propriété TextureCoordinates de la
géométrie MeshGeometry3D permet d’établir la correspondance entre les sommets
du maillage et les points 2D du contenu d’un pinceau évolué (tel que VisualBrush
ou ImageBrush). Pour exécuter ce placage, il faut tenir compte du fait que l’axe Y en
2D est inversé par rapport à l’axe Y en 3D. Les coordonnées 2D sont exprimées en
relatif par une valeur entre 0 et 1. La figure 5.4 visualise les coordonnées de la texture
à appliquer, ce qui donne une propriété TextureCoordinates = «0 1, 1 1, 0 0» pour
appliquer un ImageBrush dont la propriété ImageSource pointe sur la ressource image
«image_texture/texture_triangle_1.png».
FIGURE 5.4
0,0 1,0
0,1
1,1
142 Programmez des jeux vidéo 3D avec C#5 et WPF
La glissière x_slider_camera_axe_y, de type Slider, permet de faire tourner la caméra
autour de l’axe Y par rotation d’angle de pas de 1 unité (figures 5.5 et 5.6). Cela s’effectue
en affectant à la propriété Transform de la caméra une rotation RotateTransform3D qui
reçoit en paramètre une rotation d’axe AxisAngleRotation3D.
FIGURE 5.5
Y
Comme le montre la figure 5.8, le maillage d’un rectangle est réalisé à partir de deux
triangles. Le premier triangle est composé des points (0,1,0), (0,0,0) et (1,0,0) avec les
indices respectifs 0, 1 et 2. Le deuxième triangle est composé des points (1,0,0), (1,1,0)
et (0,1,0) avec les indices respectifs 2, 3 et 0. Le maillage, de type MeshGeometry3D, a
la propriété Positions = «0 1 0, 0 0 0, 1 0 0, 1 1 0».
FIGURE 5.8 (0,1,0) (1,1,0)
indice 0 indice 3
triangle triangle
(0,1,0) indice 0 (1,0,0) indice 2
(0,0,0) indice 1 (1,1,0) indice 3
(1,0,0) indice 2 (0,1,0) indice 0
(0,0,0) (1,0,0)
indice 1 indice 2
Pour appliquer un placage de texture sur la face avant, il faut définir la propriété
Material de GeometryModel3D en lui affectant un objet DiffuseMaterial par exemple.
Puis il faut appliquer à la propriété Brush de DiffuseMaterial, un objet ImageBrush qui
correspond à la texture à appliquer. Cet ImageBrush a sa propriété ImageSource qui
référence une image (ici elle référence l’image image_texture/texture_triangle_1.png).
Le procédé est le même pour appliquer un placage de texture sur la face arrière en
définissant la propriété BackMaterial de GeometryModel3D.
Ce placage de texture ne peut être réalisé que si la propriété TextureCoordinates
est explicitement exprimée. La propriété TextureCoordinates de la géométrie
MeshGeometry3D permet d’établir la correspondance entre les sommets du maillage
et les points 2D du contenu d’un pinceau évolué (tel que VisualBrush ou ImageBrush).
Pour exécuter ce placage, il faut tenir compte du fait que l’axe Y en 2D est inversé par
rapport à l’axe Y en 3D. Les coordonnées 2D sont exprimées en relatif par une valeur
entre 0 et 1. La figure 5.9 visualise les coordonnées de la texture à appliquer, ce qui
donne une propriété TextureCoordinates = «0 0, 0 1, 1 1, 1 0».
On aura par exemple, pour le rectangle en haut et à droite, qui possède une propriété
Material recevant un placage de texture sous forme d’une ressource image et une
146 Programmez des jeux vidéo 3D avec C#5 et WPF
propriété BackMaterial recevant une couleur jaune, le code suivant:
<!-- rectangle haut droit -->
<ModelUIElement3D x:Name=ˈx_modele_hdˈ>
<Model3DGroup x:Name=ˈx_groupe_hdˈ>
<GeometryModel3D x:Name=ˈx_geo_hdˈ>
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions=ˈ0 1 0, 0 0 0, 1 0 0, 1 1 0ˈ
TriangleIndices=ˈ0 1 2, 2 3 0ˈ
TextureCoordinates=ˈ0,0 0,1 1,1 1,0ˈ></MeshGeometry3D>
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource=ˈimage_texture/texture_triangle_1.pngˈ />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
<GeometryModel3D.BackMaterial>
<DiffuseMaterial Brush=ˈYellowˈ></DiffuseMaterial>
</GeometryModel3D.BackMaterial>
</GeometryModel3D>
...
</ModelUIElement3D>
FIGURE 5.9
(0,1,0) (1,1,0)
indice 0 indice 3
(1,0)
(0,0)
(0,1)
(1,1) (0,0,0) (1,0,0)
indice 1 indice 2
On aura par exemple (figure 5.10), pour le rectangle en haut et à gauche, qui possède
CHAPITRE 5 Les modèles 3D basiques 147
une propriété Material recevant un placage de texture sous forme d’un dégradé de
couleurs et une propriété BackMaterial recevant une couleur jaune, le code suivant:
<!-- rectangle haut gauche -->
<ModelUIElement3D x:Name=ˈx_modele_hgˈ>
<Model3DGroup x:Name=ˈx_groupe_hgˈ>
...
<GeometryModel3D.Material>
<DiffuseMaterial x:Name=ˈx_surface_triangle_hgˈ>
<DiffuseMaterial.Brush>
<LinearGradientBrush x:Name=ˈmyGradientBrushˈ StartPoint=ˈ0,0.5ˈ
EndPoint=ˈ1,0.5ˈ>
<LinearGradientBrush.GradientStops>
<GradientStop Color=ˈYellowˈ Offset=ˈ0ˈ />
<GradientStop Color=ˈRedˈ Offset=ˈ0.25ˈ />
<GradientStop Color=ˈBlueˈ Offset=ˈ0.75ˈ />
<GradientStop Color=ˈLimeGreenˈ Offset=ˈ1ˈ />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
<GeometryModel3D.BackMaterial>
<DiffuseMaterial Brush=ˈYellowˈ></DiffuseMaterial>
</GeometryModel3D.BackMaterial>
</GeometryModel3D>
...
</ModelUIElement3D>
FIGURE 5.10
(0,1,0) (1,1,0)
indice 0 indice 3
(1,0)
(0,0)
(0,1)
(1,1) (0,0,0) (1,0,0)
indice 1 indice 2
148 Programmez des jeux vidéo 3D avec C#5 et WPF
La face avant des quatre rectangles est éclairée par une lumière de type SpotLight.
On lui fixe sa propriété Color à White. On fixe sa propriété Position par un Point3D
de coordonnées (0,0,6). On règle la direction de l’axe du cône de lumière en le
faisant pointer sur l’origine du repère. Il faut donc affecter un vecteur Vector3D dont
l’orientation va de la position du spot au repère d’origine (propriété Direction affectée
par un Vector3D(0,0,-5) représentant cette direction). On règle les angles du cône qui
éclaire la scène en fixant les propriétés InnerConeAngle et OuterConeAngle. La figure
5.11 visualise les propriétés du cône de lumière de type SpotLight et l’effet obtenu
dans l’exemple.
<ModelUIElement3D x:Name=ˈx_lumiereˈ>
<Model3DGroup>
<SpotLight Color=ˈWhiteˈ Position=ˈ0,0,6ˈ Direction=ˈ0,0,-5ˈ
InnerConeAngle=ˈ20ˈ OuterConeAngle=ˈ35ˈ></SpotLight>
<DirectionalLight Color=ˈWhiteˈ Direction=ˈ0,0,1ˈ></DirectionalLight>
</Model3DGroup>
</ModelUIElement3D>
FIGURE 5.11
propriété
OuterConeAngle
propriété InnerConeAngle propriété
Direction
propriété
OuterConeAngle
propriété InnerConeAngle
Copyright 2013 Patrice REY
CHAPITRE 5 Les modèles 3D basiques 149
La face arrière des quatre rectangles est éclairée par une lumière directionnelle de
type DirectionalLight. Sa propriété Color donne la couleur de la lumière et sa propriété
Direction indique le sens d’envoi de la lumière (sous forme d’un Vector3D).
<ModelUIElement3D x:Name=ˈx_lumiereˈ>
<Model3DGroup>
<SpotLight Color=ˈWhiteˈ Position=ˈ0,0,6ˈ Direction=ˈ0,0,-5ˈ
InnerConeAngle=ˈ20ˈ OuterConeAngle=ˈ35ˈ></SpotLight>
<DirectionalLight Color=ˈWhiteˈ Direction=ˈ0,0,1ˈ></DirectionalLight>
</Model3DGroup>
</ModelUIElement3D>
FIGURE 5.12 Y
Y
Y Y
150 Programmez des jeux vidéo 3D avec C#5 et WPF
La figure 5.12 visualise le résultat obtenu quand on fait tourner la caméra autour de
la scène. On voit bien l’incidence de l’éclairage du spot sur la face avant des quatre
rectangles et l’incidence de l’éclairage de la lumière directionnelle sur la face arrière
des quatre rectangles.
2 - La modélisation du cube
La modélisation d’un volume consiste à passer par la réalisation de son maillage, puis
consiste à appliquer sur ce maillage une texture (sous forme d’une couleur unie, sous
forme d’un dégradé de couleurs, ou sous forme d’un placage de texture en général par
application d’une image prédéfinie).
L’UserControl ModeleCube.xaml, dans le dossier chapitre_05, visualise un ensemble de
quatre cubes (figure 5.13) pour lesquels on a appliqué des placage de texture différents
(texture avec motif, texture avec écriture, texture avec photographies et texture avec
une imitation de bois). Une caméra est positionnée dans l’espace 3D, elle vise l’origine
du repère 3D. Trois glissières permettent de faire tourner la caméra autour des axes X,
Y et Z. La scène est illuminée au moyen d’un éclairage ambiant.
Le maillage d’un volume consiste à trouver toutes les facettes triangulaires qui le
composent. Une facette triangulaire est composée de trois points dans l’espace 3D.
Une fois les facettes triangulaires localisées, on peut alors appliquer dessus un placage
de texture. Un cube est composé de six faces. Chaque face peut être réalisée au moyen
de deux triangles accolés par leur hypothénuse.
Y
1
Copyright 2013 Patrice REY
X
2
Z
CHAPITRE 5 Les modèles 3D basiques 151
FIGURE 5.13
Différents cubes pour lesquels on a appliqué
des placages de texture prédéfinis
face
dessus
2 1
6 2 2 1 1 5 5 6
7 3 3 0 0 4 4 7
Copyright 2013 Patrice REY
3 0
face
dessous
7 4
CHAPITRE 5 Les modèles 3D basiques 153
FIGURE 5.14
6 (0,y,0) 5 (x,y,0)
face
gauche face
dessus
6 (0,y,0) 2 (0,y,z)
2 (0,y,z) 1 (x,y,z)
7 (0,0,0) 3 (0,0,z)
face
(0,y,0) (x,y,0) arrière
6 5
5 (x,y,0) 6 (0,y,0)
(0,y,z) (x,y,z)
2 1 4 (x,0,0) 7 (0,0,0)
7 4
(0,0,0) (x,0,0)
X
3
0 1 (x,y,z) 5 (x,y,0)
(0,0,z) (x,0,z)
face
droite
Z 0 (x,0,z) 4 (x,0,0)
face face
avant dessous
6 6 0 2 2 2 4 1
0 3 4 7
1 2 2 5 6 6
7 3 3 3 0 0
5 5 12 6 1 1 8 5
12 15 8 11
13 14 14 9 10 10
4 7 7 0 4 4
6 6 16 5 3 3 20 0
Copyright 2013 Patrice REY
16 19 20 23
17 18 18 21 22 22
2 1 1 7 4 4
On définit une méthode MaillageCube qui reçoit en paramètre une largeur de côté cote
de type double et qui retourne la géométrie du maillage, intitulée maillage, de type
MeshGeometry3D. Les 8 sommets sont exprimés par des structures Point3D intitulées
pt_0 à pt_7.
private MeshGeometry3D MaillageCube(double cote) {
MeshGeometry3D maillage = new MeshGeometry3D();
Point3D pt_0 = new Point3D(cote, 0, cote);
Point3D pt_1 = new Point3D(cote, cote, cote);
Point3D pt_2 = new Point3D(0, cote, cote);
Point3D pt_3 = new Point3D(0, 0, cote);
Point3D pt_4 = new Point3D(cote, 0, 0);
Point3D pt_5 = new Point3D(cote, cote, 0);
Point3D pt_6 = new Point3D(0, cote, 0);
Point3D pt_7 = new Point3D(0, 0, 0);
...
return maillage;
}
On ajoute les sommets de chaque face à la propriété Positions de l’objet maillage dans
l’ordre indiqué sur la figure 5.16 (sens contraire des aiguilles d’une montre).
private MeshGeometry3D MaillageCube(double cote) {
MeshGeometry3D maillage = new MeshGeometry3D();
...
//6 7 3 2 face gauche
maillage.Positions.Add(pt_6);
maillage.Positions.Add(pt_7);
maillage.Positions.Add(pt_3);
maillage.Positions.Add(pt_2);
//2 3 0 1 face avant
maillage.Positions.Add(pt_2);
maillage.Positions.Add(pt_3);
maillage.Positions.Add(pt_0);
maillage.Positions.Add(pt_1);
//1 0 4 5 face droite
maillage.Positions.Add(pt_1);
maillage.Positions.Add(pt_0);
maillage.Positions.Add(pt_4);
maillage.Positions.Add(pt_5);
//5 4 7 6 face arriere
maillage.Positions.Add(pt_5);
maillage.Positions.Add(pt_4);
maillage.Positions.Add(pt_7);
maillage.Positions.Add(pt_6);
//6 2 1 5 face dessus
maillage.Positions.Add(pt_6);
maillage.Positions.Add(pt_2);
maillage.Positions.Add(pt_1);
156 Programmez des jeux vidéo 3D avec C#5 et WPF
maillage.Positions.Add(pt_5);
//3 7 4 0 face dessous
maillage.Positions.Add(pt_3);
maillage.Positions.Add(pt_7);
maillage.Positions.Add(pt_4);
maillage.Positions.Add(pt_0);
...
return maillage;
}
Puis il faut générer les indices des sommets que l’on doit ajouter à la propriété
TriangleIndices. La propriété Positions contient une collection de Point3D. Avec une
boucle for, on parcourt cette collection par pas de 4, et on ajoute un nouvel indice à
partir de l’indice 0 de base. On s’aide d’une variable depart qui délivre les indices à la
demande. Pourquoi utiliser un pas de 4? Parce que lorsque l’on a 2 triangles accolés l’un
à l’autre avec 4 sommets aux indices 0, 1, 2 et 3, on doit passer en premier la séquence
012 du premier triangle puis en deuxième la séquence 230 du second triangle.
//generer les indices pour la suite de triangles
private void GenererTriangleIndice(MeshGeometry3D maillage) {
int depart = 0;
for (int xx = 0; xx < maillage.Positions.Count; xx += 4) {
maillage.TriangleIndices.Add(depart);
maillage.TriangleIndices.Add(depart + 1);
maillage.TriangleIndices.Add(depart + 2);
maillage.TriangleIndices.Add(depart + 2);
maillage.TriangleIndices.Add(depart + 3);
maillage.TriangleIndices.Add(depart);
depart += 4;
}
}
face
dessus
(0,1/3)
(0,2/3)
face
dessous
(0,1)
(1,1)
(0,1/3) (1/4,1/3)
face
gauche
(0,2/3) (1/4,2/3)
FIGURE 5.18
face
dessus
ressource image
texture_cube_1.png
de taille 1000x746 face face face face
pixels
gauche avant droite arrière
face
dessous
ressource image
texture_cube_2.png de taille ressource image texture_cube_4.png de
340x255 pixels taille 1000x750 pixels
ressource image
texture_cube_3.png de taille
340x255 pixels
160 Programmez des jeux vidéo 3D avec C#5 et WPF
un objet ModelUIElement3D qui correspond au cube texturé qui peut alors être
positionné directement sur la scène.
L’objet ModelUIElement3D nommé cube est instancié. On affecte à sa propriété Model
une géométrie de maillage geo_cube de type GeometryModel3D. La propriété Geometry
de geo_cube reçoit le maillage MeshGeometry3D de la méthode MaillageCube. La
propriété Material de geo_cube reçoit la texture de placage de la face vue, et la propriété
BackMaterial de geo_cube reçoit la texture de placage de la face cachée.
Pour appliquer une texture sous forme d’une image, on instancie un ImageBrush et
on affecte à sa propriété ImageSource un objet BitmapImage dont on fixe le chemin
sous forme d’un URI. Pour référencer une image embarquée dans l’application, on
utilise le format d’URI absolu suivant: new Uri(«pack://application:,,/» + chemin_texture,
UriKind.Absolute) qui référence une image dans l’arborescence des fichiers. Puis l’objet
ImageBrush est affecté à la propriété Brush d’un objet DiffuseMaterial, lui-même étant
affecté à la propriété Material de geo_cube.
Pour translater le cube sur la scène, on utilise un objet TranslateTransform3D qui
reçoit en paramètre les déplacements deplac_x, deplac_y et deplac_z. Puis on l’affecte à
la propriété Transform de cube.
private ModelUIElement3D AjouterObjetCube(double largeur_cote,
string chemin_texture, double deplac_x, double deplac_y, double deplac_z) {
ModelUIElement3D cube = new ModelUIElement3D();
GeometryModel3D geo_cube = new GeometryModel3D();
geo_cube.Geometry = MaillageCube(largeur_cote);
DiffuseMaterial diffus = new DiffuseMaterial();
ImageBrush img_brush = new ImageBrush();
img_brush.ImageSource = new BitmapImage(
new Uri(«pack://application:,,/» + chemin_texture, UriKind.Absolute));
diffus.Brush = img_brush;
geo_cube.Material = diffus;
geo_cube.BackMaterial = new DiffuseMaterial(Brushes.Yellow);
cube.Model = geo_cube;
cube.Transform = new TranslateTransform3D(deplac_x,deplac_y,deplac_z);
return cube;
}
Copyright 2013 Patrice REY
2
164 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 5.21
166 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 5.22
6 (-x,+y,-z) 5 (+x,+y,-z) 5 4 7 6
face face
dessus arrière
4 (+x,-y,-z) 7 (-x,-y,-z)
Y
7 (-x,-y,-z) 3 (-x,-y,+z) 6
(-x,+y,-z) 5 (+x,+y,-z)
6 7 3 2
1
(-x,+y,+z) 2 (+x,+y,+z)
7 X
(-x,-y,-z) 4 (+x,-y,-z)
1 (+x,+y,+z) 5 (+x,+y,-z)
face
(-x,-y,+z) 3 0 droite
Z (+x,-y,+z)
0 (+x,-y,+z) 4 (+x,-y,-z)
3 (-x,-y,+z) 0 (+x,-y,+z) 1 0 4 5
2 (-x,+y,+z) 1 (+x,+y,+z)
Copyright 2013 Patrice REY
face face
avant dessous
2 3 0 1 3 7 4 0
CHAPITRE 5 Les modèles 3D basiques 167
}
if (etat_visibilite == «afficher») {
conteneur_axe.Visibility = Visibility.Visible;
}
break;
}
}
}
CHAPITRE 5 Les modèles 3D basiques 169
Width=ˈ439ˈ FontSize=ˈ16ˈ>
<TreeViewItem x:Name=ˈx_arbo_doss_cameraˈ Header=ˈDossier caméraˈ></TreeViewItem>
<TreeViewItem x:Name=ˈx_arbo_doss_sceneˈ Header=ˈDossier scène 3Dˈ>
</TreeViewItem>
</TreeView>
Par un parcours des éléments contenus dans le conteneur principal x_conteneur_3d, on
récupère le nom des conteneurs stockés et on instancie un TreeViewItem qui reçoit
une chaîne texte contenant ce nom. Ensuite il est ajouté au TreeView dans le dossier
CHAPITRE 5 Les modèles 3D basiques 171
3 - La modélisation du cylindre
Le cylindre est un solide avec une surface cylindrique délimitée par deux plans
parallèles. Nous allons voir la modélisation du cylindre circulaire creux (le tube par
exemple) après avoir abordé la notion de coordonnée sphérique.
Les coordonnées cartésiennes d’un point, dans l’espace 2D, s’expriment par 2
coordonnées (x,y,) selon les 2 axes. Tous les points qui se situent à une distance R de
Copyright 2013 Patrice REY
R φ
X
O xa
Le cylindre est un solide avec une surface cylindrique délimitée par deux plans
parallèles. Une surface cylindrique est formée par le déplacement parallèle d’une
droite le long d’une courbe fermée. La figure ci-dessous visualise géométriquement la
constitution d’un cylindre circulaire plein.
Y
B
Rg = rayon du cercle externe
point A:
xa = Rg*cos(φ)
ya = 0
za = -Rg*sin(φ)
cas du point B:
cylindre xb = Rg*cos(φ) = xa
plein: yb = H
zb = -Rg*sin(φ) = za
A
za
O φ
X
xa
Z
174 Programmez des jeux vidéo 3D avec C#5 et WPF
Dans le cas du cylindre circulaire plein, le point A peut être repéré par ses coordonnées
sphériques (avec Rg le rayon du cercle et φ l’angle de rotation):
xa = Rg * cos(φ)
ya = 0
za = -Rg * sin(φ)
Le point B a des coordonnées similaires à part une hauteur différente (H hauteur du
cylindre) d’où yb=H.
xb = Rg * cos(φ)
yb = H
zb = -Rg * sin(φ)
Le cylindre creux est similaire au cylindre plein avec un cercle extérieur de rayon Rg
et un cercle intérieur plus petit de rayon Rp. La position des points se calculera de la
même façon en choisissant le rayon Rg ou Rp d’appartenance du point considéré.
Y
D
C
B
O X
point A: point B:
Copyright 2013 Patrice REY
xa = Rg*cos(φ) xb = Rg*cos(φ)
ya = H ya = 0
za = -Rg*sin(φ) zb = -Rg*sin(φ)
Partant de là, on s’aperçoit déjà que l’on va devoir modéliser quatre facettes dans le
cas général (cas du cylindre creux). Le cylindre plein sera en fait une exception, puisque
l’on choisira un rayon interne Rp de quantité nulle.
CHAPITRE 5 Les modèles 3D basiques 175
Il y a donc quatre facettes à générer pour modéliser la facette cylindrique à faire tourner
autour d’un axe vertical de façon générale (figure 5.25). Les facettes de quatre points,
qui sont composées de deux triangles, sont (avec l’ordre de celui du sens contraire des
aiguilles d’une montre):
• la facette extérieure ABCD,
• la facette du dessus HADE,
• la facette intérieure EFGH,
• la facette du dessous BGFC.
La figure 5.25 visualise les coordonnées de chacun de ces points en fonction de leur
position externe ou interne.
FIGURE 5.25
D
E point A:
xa = Rg*cos(φ)
A ya = H
H za = -Rg*sin(φ)
point B:
facette ABCD
xb = Rg*cos(φ)
ya = 0
facette HADE zb = -Rg*sin(φ)
point F:
facette EFGH
xf = Rp*cos(φ)
yf = 0
facette BGFC zf = -Rp*sin(φ)
point E:
xe = Rp*cos(φ)
C ye = H
F ze = -Rp*sin(φ)
B
G
On commence par définir la géométrie de la facette extérieure ABCD par la méthode
MaillageCylindreFaceExterne, qui reçoit en paramètre la hauteur du cylindre (hauteur
de type double), le rayon du cercle extérieure (ray_ext de type double) et le nombre
de division correspondant au nombre de facettes voulues (nb_div de type int). L’angle
176 Programmez des jeux vidéo 3D avec C#5 et WPF
φ sera calculé en fonction du nombre de division. Pour cela on définit un pas d’angle
pas_angle, de type double, égal à la division de 360° par nb_div. Avec une boucle
for on parcourt la plage d’angle de 0° à 360° par pas de pas_angle. On calcule les
coordonnées des quatre sommets pt_a, pt_b, pt_c et pt_d de la facette (figure 5.26), de
type Point3D, par des coordonnées sphériques. Pour le calcul des sinus et cosinus, on
utilise les méthodes statiques Math.Sin et Math.Cos de la classe System.Math. L’angle
demandé par ces méthodes statiques est de type double pour représenter un angle
en radians. Donc il suffit de multiplier l’angle en degrés par Math.Pi/180 pour obtenir
l’angle en radians. Les quatre points obtenus sont ajoutés à la propriété Positions du
MeshGeometry.
FIGURE 5.26
Y
D +
A A D
face cachée
C face vue
φ
X
B B C
Z
pt_c.Y = 0;
pt_c.Z = -ray_ext * Math.Sin((angle + pas_angle) * Math.PI / 180);
Point3D pt_d = new Point3D();
pt_d.X = ray_ext * Math.Cos((angle + pas_angle) * Math.PI / 180);
pt_d.Y = hauteur;
pt_d.Z = -ray_ext * Math.Sin((angle + pas_angle) * Math.PI / 180);
maillage.Positions.Add(pt_a);
maillage.Positions.Add(pt_b);
maillage.Positions.Add(pt_c);
maillage.Positions.Add(pt_d);
...
}
...
return maillage;
}
On utilise la méthode GenererTriangleIndice pour générer l’ordre des indices à utiliser
pour la suite des triangles (comme on l’a déjà vu pour le cube).
//generer les indices pour la suite de triangles
private void GenererTriangleIndice(MeshGeometry3D maillage) {
int depart = 0;
for (int xx = 0; xx < maillage.Positions.Count; xx += 4) {
maillage.TriangleIndices.Add(depart);
maillage.TriangleIndices.Add(depart + 1);
maillage.TriangleIndices.Add(depart + 2);
maillage.TriangleIndices.Add(depart + 2);
maillage.TriangleIndices.Add(depart + 3);
maillage.TriangleIndices.Add(depart);
depart += 4;
}
}
Pour générer le cylindre, on ajoute une méthode AjouterObjetCylindre, qui retourne
la modélisation du cylindre sous forme d’un ModelUIElement3D, et qui reçoit, pour
l’instant, en paramètre une hauteur de cylindre, un rayon extérieure et un nombre
de division. La facette cylindrique est composée de quatre facettes à générer. Il
s’agit donc d’une modélisation composite. On définit donc un ModelUIElement3D
cylindre dont sa propriété Model reçoit un ensemble de modélisations regroupées
dans un Model3DGroup gp_geo_cyl. Les quatre facettes seront modélisées par
un GeometryModel3D et elles seront ajoutées à la propriété Children du groupe
gp_geo_cyl. On procède ainsi car un GeometryModel3D possède une géométrie
(propriété Geometry), une texture de face avant (Material) et une texte de face arrière
(BackMaterial). Le repère 1 de la figure 5.27 visualise le résultat obtenu dans le cas d’un
nombre de division égal à 10 (une couleur verte est affectée à Material et une couleur
jaune est affectée à BackMaterial) et le repère 2 de la figure 5.27 montre la différence
178 Programmez des jeux vidéo 3D avec C#5 et WPF
de lissage obtenue entre un nombre de division de 10 et de 40. Avec 40 divisions, on
obtient un résultat très acceptable.
FIGURE 5.27
D
A 72°
1 soit 2 divisions
C
36°
pas_angle soit 1 division
Z
B
2
Copyright 2013 Patrice REY
//modeliser un cylindre
private ModelUIElement3D AjouterObjetCylindre(double hauteur, double ray_ext,
int nb_div) {
ModelUIElement3D cylindre = new ModelUIElement3D();
Model3DGroup gp_geo_cyl = new Model3DGroup();
//modeliser face exterieure
GeometryModel3D geo_cyl_face_ext = new GeometryModel3D();
geo_cyl_face_ext.Geometry = MaillageCylindreFaceExterne(hauteur, ray_ext,
nb_div);
gp_geo_cyl.Children.Add(geo_cyl_face_ext);
geo_cyl_face_ext.Material = new DiffuseMaterial(Brushes.Green);
geo_cyl_face_ext.BackMaterial = new DiffuseMaterial(Brushes.Yellow);
//
cylindre.Model = gp_geo_cyl;
return cylindre;
}
Maintenant que la face extérieure est complètement modélisée, on va pouvoir
ajouter les coordonnées pour le placage de texture (propriété TextureCoordinates de
MeshGeometry3D).
Ici, nous allons appliquer une image sur le pourtour de l’extérieur du cylindre comme
le montre la figure 5.28. On a une ressource image stockée sous forme d’un fichier
(chapitre_05/image_texture/texture_cyl_ext_1.png). Cette image a une largeur et une
hauteur. Tous les triangles de la facette sont positionnés sur cette image pour effectuer
un relevé des coordonnées de texture. Comme on l’a vu pour le cube, les coordonnées
de texture sont exprimées en relatif entre 0 et 1 suivant l’axe X et l’axe Y de l’image 2D.
Avec un compteur qui stocke le nombre de triangles générés et en fonction du nombre
de division, on détermine les coins haut gauche, haut droit, bas gauche et bas droit
d’une facette composée de 4 sommets (pt_hg, pt_hd, pt_bg et pt_bd).
//modeliser la geometrie de la face externe
private Geometry3D MaillageCylindreFaceExterne(double hauteur, double ray_ext,
int nb_div) {
MeshGeometry3D maillage = new MeshGeometry3D();
double pas_angle = 360d / nb_div;
int cpt_facette = 0;
for (double angle = 0; angle < 360; angle += pas_angle) {
...
Point pt_hg = new Point((double)cpt_facette / (double)nb_div, 0);
Point pt_hd = new Point((double)(cpt_facette + 1) / (double)nb_div, 0);
Point pt_bg = new Point((double)cpt_facette / (double)nb_div, 1);
Point pt_bd = new Point((double)(cpt_facette + 1) / (double)nb_div, 1);
maillage.TextureCoordinates.Add(pt_hg);
maillage.TextureCoordinates.Add(pt_bg);
maillage.TextureCoordinates.Add(pt_bd);
maillage.TextureCoordinates.Add(pt_hd);
180 Programmez des jeux vidéo 3D avec C#5 et WPF
cpt_facette++;
}
GenererTriangleIndice(maillage);
return maillage;
}
FIGURE 5.28
(0,0) (1,0)
(0,1) (1,1)
A D
B C
C face vue
φ
X
B A D
Z
La modélisation que nous avons utilisé pour définir un cylindre nous permet de réaliser
d’autres volumes comme ceux qui ont un pourtour avec une forme polygonale. En effet,
dans la modélisation du cylindre, on utilise un nombre de division pour augmenter le
nombre de facettes extérieures dans le but d’obtenir un lissage le plus parfait possible.
Or, si on utilise un petit nombre de division, on obtient un pourtour dont la forme est
polygonale. Par exemple un nombre de division égal à 5 donne un pourtour doté d’une
forme de pentagone (5 côtés). Avec 6, ce sera une forme d’hexagone, avec 7 une forme
d’heptagone, avec 8 une forme d’octogone, etc.
L’UserControl EnsemblePolygone.xaml, dans le dossier chapitre_05, visualise un
ensemble de 4 volumes (figure 5.31) dont un avec le pourtour en forme de pentagone,
un avec le pourtour en forme d’hexagone, un avec le pourtour en forme d’heptagone
et un avec le pourtour en forme d’octogone. Ces 4 volumes sont texturés sur chacun de
leur côté comme on l’a fait avec le cylindre (ce sont des volumes correspondant à des
«cylindres particuliers»). La figure 5.31 visualise la scène avec les 4 volumes.
pentagone hexagone
(5 côtés) (6 côtés)
heptagone
(7 côtés) octogone
(8 côtés)
186 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 5.31
pentagone hexagone
octogone heptagone
4 - La modélisation du cône
Le cône est un solide formé par l’intersection d’une surface conique avec un plan
quelconque. Une surface conique est engendrée par une droite (la génératrice) qui se
déplace le long d’une courbe (la directrice) et qui passe toujours par un point fixe (le
sommet du cône). La figure ci-dessous montre la visualisation géométrique du cône.
Y
point B:
xb = Rb * cos(φ) = xa
yb = 0
zb = -Rb * sin(φ) = za
B
za
O φ
X
xa
za = -Rb * sin(φ)
Un point A qui représente le sommet du cône dans le cas du cône circulaire droit, a
comme coordonnées (avec H la hauteur du cône):
xb = 0
yb = H
zb = 0
CHAPITRE 5 Les modèles 3D basiques 189
D
E
A
Rb = rayon du cercle bas
Rh = rayon du cercle haut
C
O B
X
Z
On s’aperçoit donc qu’il va falloir générer une facette rectangulaire (ABCD) et deux
facettes triangulaires, (EAD) et (OBC), dans le cas du cône circulaire tronqué (le cône
circulaire droit étant une exception avec le rayon du cercle du sommet de valeur nulle).
C
O B
X C
O B X
Z
Z Rb
190 Programmez des jeux vidéo 3D avec C#5 et WPF
La méthode MaillageConeFaceExterne permet de générer le maillage de la face externe
du cône en fonction d’une hauteur, d’un rayon du cercle de la base, d’un rayon du
cercle du sommet (0 si le cône n’est pas tronqué), et un nombre de division. On utilise
rigoureusement la même façon de faire que pour la face extérieure du cylindre, y
compris pour le calcul des coordonnées de texture.
//modeliser la geometrie de la face externe
private Geometry3D MaillageConeFaceExterne(double hauteur, double ray_base,
double ray_sup, int nb_div) {
MeshGeometry3D maillage = new MeshGeometry3D();
double pas_angle = 360d / nb_div;
int cpt_facette = 0;
for (double angle = 0; angle < 360; angle += pas_angle) {
Point3D pt_a = new Point3D();
pt_a.X = ray_sup * Math.Cos(angle * Math.PI / 180);
pt_a.Y = hauteur;
pt_a.Z = -ray_sup * Math.Sin(angle * Math.PI / 180);
Point3D pt_b = new Point3D();
pt_b.X = ray_base * Math.Cos(angle * Math.PI / 180);
pt_b.Y = 0;
pt_b.Z = -ray_base * Math.Sin(angle * Math.PI / 180);
Point3D pt_c = new Point3D();
pt_c.X = ray_base * Math.Cos((angle + pas_angle) * Math.PI / 180);
pt_c.Y = 0;
pt_c.Z = -ray_base * Math.Sin((angle + pas_angle) * Math.PI / 180);
Point3D pt_d = new Point3D();
pt_d.X = ray_sup * Math.Cos((angle + pas_angle) * Math.PI / 180);
pt_d.Y = hauteur;
pt_d.Z = -ray_sup * Math.Sin((angle + pas_angle) * Math.PI / 180);
maillage.Positions.Add(pt_a);
maillage.Positions.Add(pt_b);
maillage.Positions.Add(pt_c);
maillage.Positions.Add(pt_d);
Point pt_hg = new Point((double)cpt_facette / (double)nb_div, 0);
Point pt_hd = new Point((double)(cpt_facette + 1) / (double)nb_div, 0);
Point pt_bg = new Point((double)cpt_facette / (double)nb_div, 1);
Point pt_bd = new Point((double)(cpt_facette + 1) / (double)nb_div, 1);
maillage.TextureCoordinates.Add(pt_hg);
maillage.TextureCoordinates.Add(pt_bg);
maillage.TextureCoordinates.Add(pt_bd);
Copyright 2013 Patrice REY
maillage.TextureCoordinates.Add(pt_hd);
cpt_facette++;
}
GenererTriangleIndice(maillage);
return maillage;
}
Les méthodes MaillageConeFaceDessus et MaillageConeFaceDessous génèrent le
maillage de la face du dessus (cas du cône tronqué) et de la face du dessous (la base).
CHAPITRE 5 Les modèles 3D basiques 191
Elles consistent à générer un ensemble de triangles collés les uns à côté des autres par
une de leurs arêtes. Ne pas oublier d’ajouter une méthode GenererIndiceFacetteTriangle
pour générer les indices quand on passe un ensemble de facettes triangulaires.
//modeliser la geometrie de la face du dessus
private Geometry3D MaillageConeFaceDessus(double hauteur, double ray_sup,
int nb_div) {
MeshGeometry3D maillage = new MeshGeometry3D();
double pas_angle = 360d / nb_div;
int cpt_facette = 0;
for (double angle = 0; angle < 360; angle += pas_angle) {
Point3D pt_haut = new Point3D();
pt_haut.X = 0;
pt_haut.Y = hauteur;
pt_haut.Z = 0;
Point3D pt_a = new Point3D();
pt_a.X = ray_sup * Math.Cos(angle * Math.PI / 180);
pt_a.Y = hauteur;
pt_a.Z = -ray_sup * Math.Sin(angle * Math.PI / 180);
Point3D pt_d = new Point3D();
pt_d.X = ray_sup * Math.Cos((angle + pas_angle) * Math.PI / 180);
pt_d.Y = hauteur;
pt_d.Z = -ray_sup * Math.Sin((angle + pas_angle) * Math.PI / 180);
maillage.Positions.Add(pt_haut);
maillage.Positions.Add(pt_a);
maillage.Positions.Add(pt_d);
Point pt_centre = new Point(0.5, 0.5);
Point pt_centre_g = new Point(0,1);
Point pt_centre_d = new Point(1,1);
maillage.TextureCoordinates.Add(pt_centre);
maillage.TextureCoordinates.Add(pt_centre_g);
maillage.TextureCoordinates.Add(pt_centre_d);
cpt_facette++;
}
//
GenererIndiceFacetteTriangle(maillage);
return maillage;
}
//generer les indices pour la suite de triangles
private void GenererIndiceFacetteTriangle(MeshGeometry3D maillage) {
int depart = 0;
for (int xx = 0; xx < maillage.Positions.Count; xx += 3) {
maillage.TriangleIndices.Add(depart);
maillage.TriangleIndices.Add(depart + 1);
maillage.TriangleIndices.Add(depart + 2);
depart += 3;
}
}
192 Programmez des jeux vidéo 3D avec C#5 et WPF
La figure 5.32 visualise la géométrie du cône dans le cas d’un cône tronquée avec un
nombre de division égal à 10. Y
FIGURE 5.32
nombre de division = 10
D
A
36°
soit 1 division
Z B
X
C
D
Copyright 2013 Patrice REY
pt_Bas
pt_Haut
A B
CHAPITRE 5 Les modèles 3D basiques 193
FIGURE 5.33
5 - La modélisation de la sphère
Une sphère est composée de facettes triangulaires qui sont localisées dans l’espace
3D et assemblées entre elles. Les sommets des triangles doivent être localisés par
rapport au centre de la sphère. Tous les points d’une sphère ont une caractéristique
unique, c’est qu’ils se trouvent à une distance fixe d’un centre. Les points d’une sphère
appartiennent à une surface qui se trouve à une distance R d’un centre 0 (R est le rayon
de la sphère et O est le centre de la sphère).
Les coordonnées cartésiennes d’un point M s’expriment par trois coordonnées (x,y,z)
selon les 3 axes. Or, pour balayer selon des angles à une distance R, en vue de l’obtention
de coordonnées, il va falloir exprimer (x,y,z) en coordonnées sphériques (comme on l’a
vu en préalable au paragraphe concernant le cylindre).
Comme le montre géométriquement le haut de la figure 5.34, il y a une infinité de point
M de coordonnées (x,y,z) qui sont positionnés à une distance R du centre O. Dans le
plan (OX,OZ), l’ensemble des points M à une distance R (les points B et C par exemple)
correspond au balayage de l’angle ϕ quand celui-ci varie de 0 à 2π (avec l’angle β égal
à 0). Dans un plan quelconque, l’ensemble des points M à une distance R (les points A
et D par exemple) correspond au balayage de l’angle ϕ quand celui-ci varie de 0 à 2π,
avec le balayage de l’angle β qui varie de -π/2 à +π/2.
On s’aperçoit donc que l’on peut construire des facettes, comme celle dont les coins
sont A, B, C et D. Et par conséquent, on peut définir un nombre de facettes à la fois sur le
plan horizontal et sur le plan vertical. Un nombre identique de facettes horizontalement
et verticalement, conduira à avoir des facettes généralement de forme carré, sauf à
l’approche des pôles de la sphère. Un nombre important de facettes, horizontalement
et verticalement, permettra une meilleure définition de la sphère.
Le schéma géométrique du bas de la figure 5.34 nous permet de définir les coordonnées
sphériques d’un point M quelconque appartenant à la sphère. Soit un point M
appartenant à la sphère, et par conséquent, se trouvant à une distance R du centre O.
On effectue les projections orthogonales du point M sur les axes (X,Y,Z).
196 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 5.34
D
A
β
C
O φ X
B
Y
M(x,y,z)
ym points M:
xm = OP * cos(φ) = R * cos(β) * cos(φ)
R ym = R * sin(β)
zm = -OP * sin(φ) = -R * cos(β) * sin(φ)
zm P(x,0,z)
β
Copyright 2013 Patrice REY
O φ
xm
X
Z
CHAPITRE 5 Les modèles 3D basiques 197
Cela conduit à déterminer les coordonnées du point M qui sont xm, ym et zm:
• dans le triangle (O,P,xm), rectangle en xm, on a xm = OP * cos(ϕ); dans le triangle
(O,M,P), rectangle en P, on a la distance OP = R * cos(β); on en déduit donc que la
coordonnée xm = OP * cos(ϕ) = R * cos(β) * cos(ϕ).
• dans le triangle (O,M,P), rectangle en P, on a la distance MP = R * sin(β); comme la
distance MP est égale justement à ym, on a donc ym = R * sin(β).
• dans le triangle (O,P,xm), rectangle en xm, on a la distance Pxm = zm = -OP * sin(ϕ);
comme OP = R * cos(β), on en déduit que zm = -R * cos(β) * sin(β).
Ainsi on a pu exprimer les coordonnées cartésiennes (xm,ym,zm) en fonction du triplet
(R,ϕ,β). Ce triplet constitue les coordonnées sphériques du point M.
La modélisation sphérique consiste à générer les facettes de la sphère, et en particulier,
à calculer les coordonnées sphériques de quatre points qui constituent les coins de
la facette. Chaque facette, dont les points sont A, B, C et D, est composée de deux
triangles avec le premier triangle (A,B,C) et le deuxième triangle (C,D,A). Comme on
l’a déjà vu, on utilise cet ordre, dans le sens contraire des aiguilles d’une montre, pour
signifier que c’est la face que l’on voit et qui est texturée (celle de devant). La figure
ci-dessous montre les points A, B, C et D, pour une facette avec une division de l’angle
ϕ et une division de l’angle β (avec un nombre de division égal à 4, horizontalement et
verticalement, la portion de la sphère est générée quand l’angle ϕ varie de 0 à π/2 et
l’angle β varie de -π/2 à +π/2).
Y
division verticale = 4
division horizontale = 4
A
C
β
Z B
φ
X
198 Programmez des jeux vidéo 3D avec C#5 et WPF
La figure ci-dessous montre cette portion de sphère générée sous un autre angle de
visualisation.
X
L’UserControl ModeleSphere.xaml, dans le dossier chapitre_05, visualise une sphère
texturée avec une ressource image (figure 5.35). Un nombre de division égal à 60 est
utilisé horizontalement et verticalement, ce qui permet d’avoir un aspect lissé très
correcte de la surface sphérique. L’image qui est appliquée comme placage de texture
est composée dans sa partie haute par la représentation d’une vue panoramique de
Manhattan, et dans sa partie basse par la représentation d’un damier.
La méthode MaillageSphereFace permet d’élaborer le maillage de la surface sphérique
en fonction d’un rayon sphérique rayon, d’un nombre de division horizontale nb_
div_hori et d’un nombre de division verticale nb_div_verti. La variable pas_angle_phi
représente le pas de l’angle φ selon le plan horizontal (entre 0° et 360°), et la variable
pas_angle_beta représente le pas de l’angle β selon le plan vertical (entre -90° et +90°).
Deux boucles for imbriquées permettent de parcourir les valeurs de β pour un angle φ
Copyright 2013 Patrice REY
donné.
double pas_angle_phi = 360d / nb_div_hori;
double pas_angle_beta = 180d / nb_div_verti;
for (double angle_phi = 0; angle_phi < 360; angle_phi += pas_angle_phi) {
for (double angle_beta = 90; angle_beta > -90; angle_beta -= pas_angle_beta) {
...
}}
CHAPITRE 5 Les modèles 3D basiques 199
FIGURE 5.35
200 Programmez des jeux vidéo 3D avec C#5 et WPF
On définit les coordonnées sphériques des points pt_a, pt_b, pt_c et pt_d, de type
Point3D, en fonction des angles φ et β, et on les ajoute à la propriété Positions.
Y M(x,y,z)
ym points M:
xm = OP * cos(φ) = R * cos(β) * cos(φ)
R ym = R * sin(β)
zm = -OP * sin(φ) = -R * cos(β) * sin(φ)
zm P(x,0,z)
β
O φ
xm
X
Z
for (double angle_phi = 0; angle_phi < 360; angle_phi += pas_angle_phi) {
for (double angle_beta = 90; angle_beta > -90; angle_beta -= pas_angle_beta) {
Point3D pt_a = new Point3D();
pt_a.X = rayon * Math.Cos(angle_beta * Math.PI / 180)
* Math.Cos(angle_phi * Math.PI / 180);
pt_a.Y = rayon * Math.Sin(angle_beta * Math.PI / 180);
pt_a.Z = -rayon * Math.Cos(angle_beta * Math.PI / 180)
* Math.Sin(angle_phi * Math.PI / 180);
Point3D pt_b = new Point3D();
pt_b.X = rayon * Math.Cos((angle_beta - pas_angle_beta)
* Math.PI / 180) * Math.Cos(angle_phi * Math.PI / 180);
pt_b.Y = rayon * Math.Sin((angle_beta - pas_angle_beta) * Math.PI / 180);
pt_b.Z = -rayon * Math.Cos((angle_beta - pas_angle_beta) * Math.PI / 180)
* Math.Sin(angle_phi * Math.PI / 180);
Point3D pt_c = new Point3D();
pt_c.X = rayon * Math.Cos((angle_beta - pas_angle_beta) * Math.PI / 180)
* Math.Cos((angle_phi + pas_angle_phi) * Math.PI / 180);
pt_c.Y = rayon * Math.Sin((angle_beta - pas_angle_beta) * Math.PI / 180);
pt_c.Z = -rayon * Math.Cos((angle_beta - pas_angle_beta) * Math.PI / 180)
* Math.Sin((angle_phi + pas_angle_phi) * Math.PI / 180);
Point3D pt_d = new Point3D();
pt_d.X = rayon * Math.Cos(angle_beta * Math.PI / 180)
Copyright 2013 Patrice REY
0,1
(1/nb_div_h , 1/nb_div_v) 1,1
int cpt_div_v = 0;
int cpt_div_h = 0;
for (double angle_phi = 0; angle_phi < 360; angle_phi += pas_angle_phi) {
for (double angle_beta = 90; angle_beta > -90; angle_beta -= pas_angle_beta) {
...
Point pt_hg = new Point((double)cpt_div_h / (double)nb_div_hori,
(double)cpt_div_v / (double)nb_div_verti);
Point pt_bg = new Point((double)cpt_div_h / (double)nb_div_hori,
(double)(cpt_div_v + 1) / (double)nb_div_verti);
Point pt_bd = new Point((double)(cpt_div_h + 1) / (double)nb_div_hori,
(double)(cpt_div_v + 1) / (double)nb_div_verti);
Point pt_hd = new Point((double)(cpt_div_h + 1) / (double)nb_div_hori,
(double)cpt_div_v / (double)nb_div_verti);
maillage.TextureCoordinates.Add(pt_hg);
maillage.TextureCoordinates.Add(pt_bg);
202 Programmez des jeux vidéo 3D avec C#5 et WPF
maillage.TextureCoordinates.Add(pt_bd);
maillage.TextureCoordinates.Add(pt_hd);
cpt_div_v++;
}
cpt_div_v = 0;
cpt_div_h++;
}
La méthode AjouterObjetSphere permet d’ajouter une sphère sur la scène. La figure 5.35
représentait une sphère de rayon 1.5 unité avec 60 divisions horizontales et verticales.
//objets sphere
ModelUIElement3D sphere_1 = AjouterObjetSphere(1.5d, 60, 60,
«chapitre_05/image_texture/texture_sphere_vue.png»);
sphere_1.SetValue(NameProperty, «x_sphere_1»);
x_conteneur_3d.Children.Add(sphere_1);
Transform3DGroup sphere_1_trans = new Transform3DGroup();
sphere_1_trans.Children.Add(new TranslateTransform3D(0d, 0d, 0d));
sphere_1.Transform = sphere_1_trans;
La modélisation de l’ellipsoïde (figure 5.36) se déduit très facilement de celle de la
sphère. En effet, il suffit de distinguer le rayon sphérique global en deux rayons: un rayon
selon l’horizontal et un rayon selon la verticale. La figure 5.36 montre une ellipsoïde,
avec un rayon vertical supérieur au rayon horizontal, sur laquelle on a appliqué une
texture composée d’un motif.
La méthode MaillageSphereFace est remplacée par la méthode MaillageEllipsoideFace,
qui reçoit en paramètre un rayon selon l’axe horizontal (rayon_hori), un rayon selon
l’axe vertical (rayon_verti), un nombre de division selon l’axe horizontal (nb_div_hori) et
un nombre de division selon la verticale (nb_div_verti).
for (double angle_phi = 0; angle_phi < 360; angle_phi += pas_angle_phi) {
for (double angle_beta = 90; angle_beta > -90; angle_beta -= pas_angle_beta) {
Point3D pt_a = new Point3D();
pt_a.X = rayon_hori * Math.Cos(angle_beta * Math.PI / 180)
* Math.Cos(angle_phi * Math.PI / 180);
pt_a.Y = rayon_verti * Math.Sin(angle_beta * Math.PI / 180);
pt_a.Z = -rayon_hori * Math.Cos(angle_beta * Math.PI / 180)
* Math.Sin(angle_phi * Math.PI / 180);
Point3D pt_b = new Point3D();
Copyright 2013 Patrice REY
FIGURE 5.36
204 Programmez des jeux vidéo 3D avec C#5 et WPF
pt_c.Z = -rayon_hori * Math.Cos((angle_beta - pas_angle_beta) * Math.PI / 180)
* Math.Sin((angle_phi + pas_angle_phi) * Math.PI / 180);
Point3D pt_d = new Point3D();
pt_d.X = rayon_hori * Math.Cos(angle_beta * Math.PI / 180)
* Math.Cos((angle_phi + pas_angle_phi) * Math.PI / 180);
pt_d.Y = rayon_verti * Math.Sin(angle_beta * Math.PI / 180);
pt_d.Z = -rayon_hori * Math.Cos(angle_beta * Math.PI / 180)
* Math.Sin((angle_phi + pas_angle_phi) * Math.PI / 180);
...
}}
L’ellipsoïde de la figure 5.36 possède un rayon horizontal de 1 unité, un rayon vertical
de 1.5 unité et un nombre de division de 60 horizontalement et verticalement.
ModelUIElement3D ellipsoide_1 = AjouterObjetEllipsoide(1d,1.5d, 60, 60,
«chapitre_05/image_texture/texture_ellipsoide_1.png»);
ellipsoide_1.SetValue(NameProperty, «x_ellipsoide_1»);
x_conteneur_3d.Children.Add(ellipsoide_1);
Transform3DGroup ellipsoide_1_trans = new Transform3DGroup();
ellipsoide_1_trans.Children.Add(new TranslateTransform3D(0d, 0d, 0d));
ellipsoide_1.Transform = ellipsoide_1_trans;
6 - La modélisation du tore
Le tore est un solide engendré par la rotation d’un cercle autour d’une droite. Nous
allons voir la modélisation du tore avec notamment le tore dit «ouvert», le tore dit «à
collier nul» et le tore dit «croisé».
Le tore désigne un solide géométrique de l’espace euclidien 3D engendré par la
rotation d’un cercle C de rayon rt autour d’une droite affine D située dans son plan à
une distance Dr de son centre.
Si Dr est inférieur à rt, on est en présence d’un tore dit «croisé» (le tore ressemble
visuellement à une citrouille). Si Dr est égal à rt, on est en présence d’un tore dit «à
collier nul». Si Dr est supérieur à rt, on est en présence d’un tore dit «ouvert» (le tore
ressemble à une chambre à air). La figure 5.37 visualise géométriquement la réalisation
d’un tore dit «ouvert».
Un point A sur le tore sera localisé par ses coordonnées sphériques suivantes (avec Dr
Copyright 2013 Patrice REY
FIGURE 5.37 Y
point A:
xa = (Dr + rt * cos(β)) * cos(φ)
ya = rt * sin(β)
za = -(Dr + rt * cos(β)) * sin(φ)
A
rt
O1 φ β
X
O2
Z Dr
D
rt A
β C
B
0°
X
90°
206 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 5.38
Dr D
rt A
β C
point A:
xa = (Dr + rt * cos(β)) * cos(φ) B
φ
ya = rt * sin(β)
za = -(Dr + rt * cos(β)) * sin(φ)
X
FIGURE 5.39
270° -90°
0°
90°
Au sommaire de ce chapitre
• apporter de l’interactivité aux modèles 3D par héritage de la classe
UIElement3D
• implémentation du test d’atteinte des objets 3D dans le Viewport3D
• implémentation d’un Trackball pour visualiser la scène sous différents
angles par les déplacements de la souris
• apporter de l’interactivité à la scène par l’implémentation d’un zoom par
l’intermédiaire de la molette de la souris
• effectuer des translations et des rotations des objets 3D sur la scène
212 Programmez des jeux vidéo 3D avec C#5 et WPF
Nous avons vu que les géométries des modèles 3D pouvaient être exprimées en
XAML et en code procédural. Très souvent, il s’avère pratique de pouvoir modéliser en
XAML tout comme en code procédural, un modèle 3D avec des propriétés spécifiques
déterminées à l’avance et en fonction d’une utilisation ultérieure. Cette façon de faire
passe par une personnalisation des objets 3D avec pour but une réutilisation à volonté,
rapide et précise.
La première partie de ce chapitre traite de la façon de créer ces modèles personnalisés
et réutilisables par l’intermédiaire de la classe UIElement3D. Dans la seconde partie
de ce chapitre, nous verrons comment apporter de l’interactivité à la scène 3D et aux
objets qu’elle contient.
La création de modèles 3D est passionnante, mais faire en sorte que votre objet 3D
réalisé puisse réagir aux entrées clavier, au focus et aux événements est encore plus
attrayant. La solution consiste à faire hériter votre modèle 3D de la classe UIElement3D.
UIElement3D est une classe de base pour les implémentations au niveau du noyau
WPF, reposant sur les éléments WPF et les caractéristiques de présentation de base.
UIElement3D est une classe de base abstraite, qui est apparue avec le framework .NET
3.5, à partir de laquelle vous pouvez dériver des classes pour représenter des éléments
3D spécifiques.
La majeure partie du comportement d’entrée, de mise en focus et de traitement
d’événements pour des éléments 3D est généralement définie dans la classe
UIElement3D. Cela inclut les événements liés à l’entrée au niveau du clavier, de la
souris et du stylet, ainsi que les propriétés d’état qui s’y rattachent. Ces événements
correspondent souvent à des événements routés et de nombreux événements associés
à l’entrée présentent aussi bien une version de routage par propagation qu’une version
par tunneling. Ces événements doubles sont en général les événements les plus
intéressants pour contrôler des objets.
L’objet UIElement3D présente les fonctionnalités suivantes définies spécialement par
Copyright 2013 Patrice REY
la classe UIElement3D :
• il peut répondre à l’entrée de l’utilisateur (y compris contrôler la destination
d’envoi de l’entrée à travers la gestion d’événements de routage ou le routage de
commandes).
• il peut déclencher des événements routés qui suivent un itinéraire à travers
l’arborescence logique d’éléments.
CHAPITRE 6 L’interactivité des modèles 3D 213
2
214 Programmez des jeux vidéo 3D avec C#5 et WPF
Nous allons commencer par définir une géométrie personnalisée pour visualiser le
repère des coordonnées XYZ. La démarche consiste à créer un contrôle qui hérite de
UIElement3D, et à lui ajouter ses propriétés de dépendance spécifiques. Pour cela
nous créons une nouvelle classe intitulée Repere, qui hérite de UIElement3D. On
redéfinit la méthode héritée OnUpdateModel qui participe aux opérations de rendu en
cas de substitution dans une classe dérivée.
Lorsque vous dérivez une classe à partir de la classe UIElement3D, vous pouvez utiliser
cette méthode en combinaison avec la méthode InvalidateModel pour actualiser le
modèle de l’élément. Vous devez uniquement appeler cette méthode dans des scénarios
avancés. C’est notamment le cas si la classe dérivée possède plusieurs propriétés qui
affectent l’apparence et que vous ne souhaitez mettre à jour le modèle sous-jacent
qu’une seule fois. Au sein de la méthode OnUpdateModel, vous pouvez mettre à jour la
propriété Visual3DModel de la classe Visual3D. A noter que cette méthode ne possède
aucune implémentation par défaut dans la classe UIElement3D.
public class Repere : UIElement3D {
...
//redefinition de la methode de rendu
protected override void OnUpdateModel() {
...
}
...
}
On définit une propriété de dépendance P_Modele pour Repere et on affecte à cette
propriété un Model3DGroup intitulé groupe_axe. Ce modèle contient les trois axes X,
Y et Z.
//redefinition de la methode de rendu
protected override void OnUpdateModel() {
Model3DGroup groupe_axe = new Model3DGroup();
...
P_Modele = groupe_axe;
}
La démarche qui consiste à ajouter une propriété de dépendance s’effectue toujours
de la même façon quelque soit le type de la propriété de dépendance à définir. Prenons
Copyright 2013 Patrice REY
typeof(Color),
typeof(Repere),
new PropertyMetadata(Colors.Green, PM_CouleurAxeY));
//la propriete CouleurAxeZ
private static readonly DependencyProperty ProprieteCouleurAxeZ =
DependencyProperty.Register(«P_CouleurAxeZ»,
typeof(Color), typeof(Repere),
new PropertyMetadata(Colors.Blue, PM_CouleurAxeZ));
CHAPITRE 6 L’interactivité des modèles 3D 217
Pour ajouter en XAML une instance de Repere, il faut au préalable ajouter dans
les propriétés de l’UserControl une référence xmlns à l’espace de nom Geo.Repere,
préfixée de visuel, en écrivant: xmlns:visuel = «clr-namespace:VS2012_3d_c5_wpf45.
Geo». Ensuite on ajoute une instance en XAML par un nœud <visuel:Repere> en fixant
les valeurs des propriétés de dépendance comme par exemple <visuel:Repere x:Name
= «x_repere» P_LongueurDemiAxe = «4» P_EpaisseurAxe = «0.02»/>. Le repère alors
s’affiche dans la fenêtre du rendu (Design) en fonction de ses propriétés modifiées
comme le montre la figure ci-dessous (ou par défaut si on le souhaite).
1
218 Programmez des jeux vidéo 3D avec C#5 et WPF
La personnalisation des propriétés de dépendance peut se faire aussi directement
dans la fenêtre des propriétés (comme le montre la figure ci-dessous) dès lors que
les propriétés à modifier sont de type simple (double, Color, bool, etc.). Comme nous
avons préfixé les propriétés de dépendance par P_ lors de l’attribution de leur nom,
elles apparaissent les unes sous les autres dans la fenêtre des propriétés.
Il ne reste plus qu’à fixer le nom donné au repère comme référencement de sa propriété
Name par code procédural en écrivant x_repere.SetValue(NameProperty, «x_repere»).
Si on préfère ajouter le repère uniquement par code procédural, il suffit alors de
l’instancier et de fixer ses propriétés de dépendance aux valeurs souhaitées.
Geo.Repere repere = new Geo.Repere();
repere.SetValue(NameProperty, «x_repere»);
repere.P_LongueurDemiAxe = 6d;
repere.P_EpaisseurAxe = 0.02d;
repere.P_CouleurAxeX = Colors.Red;
repere.P_CouleurAxeY = Colors.Blue;
Copyright 2013 Patrice REY
repere.P_CouleurAxeZ = Colors.Green;
x_conteneur_3d.Children.Add(repere);
On procède de même pour l’ajout d’un plan de travail. On définit une classe PlanTravail
qui hérite de UIElement3D. On lui ajoute les propriétés de dépendance suivantes:
• P_Modele, de type Model3D, qui définit le modèle représentant la grille,
• P_LongueurX, de type double, qui définit la longueur totale de la grille centrée sur
X,
CHAPITRE 6 L’interactivité des modèles 3D 219
• P_LongueurZ, de type double, qui définit la longueur totale de la grille centrée sur
Z,
• P_LongueurPas, de type double, qui définit l’espace entre deux lignes,
• P_CouleurGrille, de type Color, qui définit la couleur de la grille.
En XAML, par un nœud <visuel:PlanTravail>, on ajoute un plan de travail et on
personnalise ses propriétés de dépendance directement en XAML, et par la fenêtre
des propriétés.
Par code procédural, on instancie un objet PlanTravail, on lui fixe sa propriété Name.
On personnalise ses propriétés P_LongueurX, P_Longueur_Z, P_LongueurPas et P_
CouleurGrille. On l’ajoute au conteneur des objets 3D de la scène, par la méthode Add,
à la propriété Children de x_conteneur_3d.
//objet plan de travail par code
Geo.PlanTravail plan_travail = new Geo.PlanTravail();
plan_travail.SetValue(NameProperty, «x_plan_travail»);
plan_travail.P_LongueurX = 8d;
plan_travail.P_LongueurZ = 6d;
plan_travail.P_LongueurPas = 1d;
plan_travail.P_CouleurGrille = Colors.Black;
x_conteneur_3d.Children.Add(plan_travail);
Au lieu d’utiliser une case à cocher pour afficher ou masquer le plan de travail et le
repère, nous allons utiliser un logo représentatif de cet état. Au départ le plan de travail
(référencé par x_plan_travail) et le repère (référencé par x_repere) sont visibles. Pour
220 Programmez des jeux vidéo 3D avec C#5 et WPF
cela, un contrôle Image affiche la représentation de la grille par la ressource image
image/logo_grille_actif.png, et un autre contrôle Image affiche la représentation du
repère par la ressource image image/logo_repere_actif.png. Les variables booléennes
v_plan_travail_visible et v_repere_visible servent à indiquer la visibilité ou non des
contrôles. Les deux contrôles Image sont dotés d’un ToolTip indiquant l’action qui se
produira si on clique gauche dessus les contrôles.
}
else {
x_vis_grille.Source = new BitmapImage(new Uri(
«pack://application:,,/chapitre_06/image/logo_grille_actif.png»,
UriKind.Absolute));
AfficherMasquerPlanTravail(«x_plan_travail», «afficher»);
x_vis_grille.ToolTip = «Masquer la grille»;
}
}
CHAPITRE 6 L’interactivité des modèles 3D 221
Elle parcourt les enfants contenus dans l’arbre visuel de x_conteneur_3d, et dès qu’elle
trouve le contrôle demandé, elle change sa propriété Visibility par Hidden (pour
masquer) ou par Visible (pour afficher).
...
//
private enum ModeAffichage { uni, texture };
private ModeAffichage v_mode_affichage;
public Cube() {
v_mode_affichage = ModeAffichage.uni;
}
//
public void PasserEnModeCouleurUni() {
CHAPITRE 6 L’interactivité des modèles 3D 223
v_mode_affichage = ModeAffichage.uni;
this.InvalidateModel();
}
//
public void PasserEnModeTexture(Uri uri_chemin_ressource) {
DiffuseMaterial diffus = new DiffuseMaterial();
ImageBrush img_brush = new ImageBrush();
img_brush.ImageSource = new BitmapImage(uri_chemin_ressource);
diffus.Brush = img_brush;
this.P_Texture = diffus;
v_mode_affichage = ModeAffichage.texture;
this.InvalidateModel();
}
//redefinition de la methode de rendu
protected override void OnUpdateModel() {
GeometryModel3D geo_cube = new GeometryModel3D();
geo_cube.Geometry = MaillageAxe(this.P_LargeurX, this.P_HauteurY,
this.P_ProfondeurZ);
switch (v_mode_affichage) {
case ModeAffichage.uni:
geo_cube.Material = new DiffuseMaterial(
new SolidColorBrush(this.P_CouleurUnie));
break;
case ModeAffichage.texture:
geo_cube.Material = this.P_Texture;
break;
}
geo_cube.BackMaterial = new DiffuseMaterial(
new SolidColorBrush(Colors.Yellow));
P_Modele = geo_cube;
}
...
}
Pour représenter une boite d’allumette, en XAML, on ajoute un cube x_cube et on
personnalise ses propriétés P_LargeurX (fixée à 2d), P_ProfondeurZ (fixée à 1.5d) et P_
couleurUnie (fixée à #FFE2A217).
<Viewport3D.Children>
<ContainerUIElement3D x:Name=ˈx_conteneur_3dˈ>
<ContainerUIElement3D.Children>
...
<visuel:PlanTravail x:Name=ˈx_plan_travailˈ P_LongueurX=ˈ6ˈ P_LongueurZ=ˈ6ˈ/>
<!-- repere xyz -->
<visuel:Repere x:Name=ˈx_repereˈ P_LongueurDemiAxe=ˈ4ˈ P_EpaisseurAxe=ˈ0.02ˈ/>
<!-- cube -->
<visuel:Cube x:Name=ˈx_cubeˈ P_ProfondeurZ=ˈ1.5ˈ P_LargeurX=ˈ2ˈ
P_CouleurUnie=ˈ#FFE2A217ˈ/>
</ContainerUIElement3D.Children>
224 Programmez des jeux vidéo 3D avec C#5 et WPF
Par code procédural on fixe la propriété Name du cube et on le passe en mode texturé
en indiquant l’URI chapitre_06/image/texture_cube_allumette.png correspondant au
fichier image de placage de texture.
//objet cube par xaml
x_cube.SetValue(NameProperty, «x_cube»);
x_cube.PasserEnModeTexture(new Uri(«pack://application:,,/» + «chapitre_06/image/
texture_cube_allumette.png», UriKind.Absolute));
L’UserControl TestAtteinte.xaml, dans le dossier chapitre_06, permet de mettre en
œuvre le test d’atteinte sur le Viewport3D (figure 6.2). La zone de rendu x_viewport
contient un repère, un plan de travail et une boite d’allumettes texturée. Cette zone de
rendu est dotée d’un gestionnaire pour l’événement MouseLeftButtonDown. Comme
le montre la figure 6.2, si on clique sur la boite d’allumettes (repère 1), une fenêtre
MessageBox s’affiche en mentionnant le nom du modèle 3D et son type. Si on clique
sur un axe du repère (repère 2), la boite MessageBox indique «Geo.Repere -> x_repere»
Copyright 2013 Patrice REY
FIGURE 6.2
3
226 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 6.3
3 - Implémenter un Trackball
En informatique, un Trackball est une boule insérée dans un clavier, que l’on manipule
avec les doigts et qui fait office de souris. Dans la modélisation 3D, reproduire les
déplacements de la souris d’un Trackball avec une incidence sur le visuel de la zone
de rendu, est incontestablement un plus, et cela apporte un niveau d’intuitivité
supplémentaire. Nous allons implémenter un Trackball pour le Viewport3D.
Le Trackball transforme les mouvements 2D de la souris sur la zone de rendu en
mouvements 3D par des rotations. Cela est possible en projetant la position de la
souris sur une sphère imaginaire positionnée derrière le Viewport3D. Quand la souris
bouge horizontalement, une rotation autour de l’axe Y est nécessaire pour viser le
même point sur la sphère positionnée derrière le pointeur de la souris.
Y Y
X
X
Z
Z
curseur de
la souris curseur de
la souris
228 Programmez des jeux vidéo 3D avec C#5 et WPF
Quand la souris bouge verticalement, une rotation autour de l’axe X est nécessaire
pour viser le même point sur la sphère positionnée derrière le pointeur de la souris.
Y
Y
X curseur de X
la souris
Z Z
curseur de
la souris
Le Trackball fournit une méthode intuitive par laquelle une scène 3D peut être
manipulée dans toutes les directions en appliquant une combinaison de rotations.
Commençons par détailler un déplacement horizontal du curseur de la souris qui passe
de la variable v_pos_souris de coordonnées (200,100) à la variable pos_souris_deplac de
coordonnées (300,100) dans l’espace 2D. Le vecteur delta_2d, pour aller de la position
initiale à la position finale, sera delta_2d = v_pos_souris - pos_souris_deplac soit (100,0).
X
v_pos_souris -> (200,100) (200,100) (300,100)
pos_souris_deplac -> (300,120)
Y
vecteur: delta_2d = v_pos_souris - pos_souris_deplac = (300-200,100-100) = (100,0)
X
(200,100) (300,100)
Copyright 2013 Patrice REY
delta_2d
Y (100,0)
Y
(200,100,0) (300,100,0)
delta_3d
(100,0,0)
X
Z
Maintenant si on calcule le produit vectoriel de delta_3d par un vecteur Z (qui représente
la direction positive de l’axe Z), on obtiendra un vecteur perpendiculaire à delta_3d et
à Z, possédant une direction et une longueur. Ce vecteur vect_axe résultant du produit
vectoriel représentera l’axe autour duquel la scène pourra tourner, et la valeur de sa
longueur représentera l’angle de rotation. Plus la différence entre la position initiale
de la souris et la position de la souris déplacée, sera grande et plus la rotation sera
importante. On a vu au premier chapitre comment calculer un produit vectoriel. Ici
nous avons delta_3d (100,0,1) et le vecteur Z(0,0,1). Le produit vectoriel donne un
vecteur vect_axe(0,-100,0).
delta_3d 100 0 0 100 0
vecteur Z 0 0 1 0 0
première troisième
coordonnée deuxième coordonnée
0*1-0*0 coordonnée 100 * 0 - 0 * 0
=0 0 * 0 - 1 * 100 = -100 =0
Y delta_3d
(100 , 0 , 0)
vecteur
Z (0,0,1)
X
vect_axe (0 , -100, 0)
= produit vectoriel de
Z delta_3d par vecteur Z
230 Programmez des jeux vidéo 3D avec C#5 et WPF
En appliquant le même principe, si on effectue un déplacement vertical du curseur de
la souris qui passe de la variable v_pos_souris de coordonnées (200,100) à la variable
pos_souris_deplac de coordonnées (200,50) dans l’espace 2D, on obtiendra un vecteur
vect_axe(50,0,0).
1 Y
X (200,50,0)
delta_3d
(200,50) (0,50,0)
delta_2d 2 (200,100,0)
(0,-50)
(200,100) X
Y
delta_3d 0 50 0 0 50
3 vecteur Z 0 0 1 0 0
première troisième
coordonnée deuxième coordonnée
50 * 1 - 0 * 0 coordonnée 0 * 0 - 0 * 50
= 50 0*0-1*0 =0
=0
Y
delta_3d
(0 , 50 , 0)
4 vect_axe (50 , 0, 0)
= produit vectoriel de
delta_3d par vecteur Z
X
Copyright 2013 Patrice REY
vecteur
Z Z (0,0,1)
vect_axe(-20,-100,0).
Y
X
(200,100)
1
2 delta_3d
(300,120) (100 , -20 , 0)
Y delta_2d X
(100,20)
première troisième
coordonnée deuxième coordonnée
-20 * 1 - 0 * 0 coordonnée 100 * 0 - 0 * (-20)
= -20 0 * 0 - 1 * 100 =0
=-100
4 vecteur
Z (0,0,1)
delta_3d
(100 , -20 , 0)
X
si on tourne la si on tourne la
molette vers nous molette vers l’avant
-> Delta < 0 -> Delta > 0
donc on se donc on s’éloigne
rapproche (zoom négatif )
(zoom positif )
236 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 6.5
FIGURE 6.7
240 Programmez des jeux vidéo 3D avec C#5 et WPF
Les variables booléennes v_logo_trackball_actif et v_logo_deplac_objet_actif permettent
d’indiquer le mode en cours d’activité (soit avec le Trackball, soit le déplacement
d’objet).
Le cylindre est du type Geo.Cylindre. Il hérite de UIElement3D. Ses propriétés sont:
• P_Modele, de type Model3D, qui représente le modèle du cylindre,
• P_Hauteur, de type double, qui représente la hauteur du cylindre (valeur par défaut
1d),
• P_RayonExterne, de type double, qui représente le rayon de la face extérieure du
cylindre (valeur par défaut 1d),
• P_RayonInterne, de type double, qui représente le rayon de la face intérieure du
cylindre (valeur par défaut 0.5d),
• P_NombreDivision, de type int, qui représente le nombre de division pour les faces
du cylindre (valeur par défaut 40),
• P_CouleurUnie, de type Color, qui représente la couleur de toutes les parties vues
du cylindre (par défaut Colors.Blue),
• P_TextureExterieure, de type Material, qui représente la texture appliquée sur la
face extérieure du cylindre en fonction d’une ressource image (valeur par défaut
null),
• P_TextureInterieure, de type Material, qui représente la texture appliquée sur la
face intérieure du cylindre en fonction d’une ressource image (valeur par défaut
null),
• P_TextureDessus, de type Material, qui représente la texture appliquée sur la face
du dessus du cylindre en fonction d’une ressource image (valeur par défaut null),
• P_TextureDessous, de type Material, qui représente la texture appliquée sur la face
du dessous du cylindre en fonction d’une ressource image (valeur par défaut null),
Les méthodes FixerTextureExterieur, FixerTextureInterieur, FixerTextureDessus et
FixerTextureDessous permettent de fixer les textures en fonction d’un URI pointant sur
une ressource image. Les méthodes AfficherObjetModeUni et AfficherObjetModeTexture
permettent de passer en mode d’affichage respectivement couleur et texture.
Le principe que nous allons utiliser ici pour déplacer les objets est simple d’un point
Copyright 2013 Patrice REY
de vue interface utilisateur. Ce qui compte c’est de retenir la démarche qui pourra être
améliorée en terme d’ergonomie. Comme le montre la figure 6.8, pour déplacer la boite
d’allumettes, on active le mode de déplacement selon les axes (repère 1). On clique
sur l’objet à déplacer (repère 2), celui-ci passe en mode couleur unie. Les boutons de
translation (repère 3) s’activent, et on clique dessus pour effectuer les translations
voulues. Quand c’est fini, on clique sur OK et la boite repasse en mode texture (repère
4). On effectue la même démarche pour positionner le cylindre sur la boite.
CHAPITRE 6 L’interactivité des modèles 3D 241
FIGURE 6.8
4
242 Programmez des jeux vidéo 3D avec C#5 et WPF
Pour sélectionner un objet 3D, on effectue un test d’atteinte sur le Viewport3D comme
on l’a déjà vu. La propriété héritée VisualHit de RayMeshGeometry3DHitTestResult
nous permet d’obtenir le type de l’objet 3D. On récupère son nom (propriété
NameProperty) que l’on stocke dans une variable v_nom_xaml_obj_select. On passe
l’objet 3D en mode couleur unie par la méthode AfficherObjetModeUni. On récupère
la matrice de transformation déjà effectuée par la propriété Transform de l’objet
3D. On stocke dans les variables v_deplac_x, v_deplac_y et v_deplac_z, les valeurs
des translations effectuées (propriétés OffsetX, OffsetY et OffsetZ de la matrice). La
méthode AfficherBoutonDeplacement active les boutons de translation.
//clic gauche enfoncé sur viewport3d
private void x_viewport_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (v_logo_deplac_objet_actif == true) {
e.Handled = true;
Point pos_souris = e.GetPosition(x_viewport);
HitTestResult result = VisualTreeHelper.HitTest(x_viewport, pos_souris);
RayMeshGeometry3DHitTestResult mesh = result as RayMeshGeometry3DHitTestResult;
if (mesh.VisualHit.GetType() == typeof(Geo.Cube)) {
Geo.Cube cube = (Geo.Cube)mesh.VisualHit;
v_nom_xaml_obj_select = (string)cube.GetValue(NameProperty);
cube.AfficherObjetModeUni();
Matrix3D mat = cube.Transform.Value;
v_deplac_x = mat.OffsetX;
v_deplac_y = mat.OffsetY;
v_deplac_z = mat.OffsetZ;
AfficherBoutonDeplacement();
}
if (mesh.VisualHit.GetType() == typeof(Geo.Cylindre)) {
Geo.Cylindre cylindre = (Geo.Cylindre)mesh.VisualHit;
v_nom_xaml_obj_select = (string)cylindre.GetValue(NameProperty);
cylindre.AfficherObjetModeUni();
Matrix3D mat = cylindre.Transform.Value;
v_deplac_x = mat.OffsetX;
v_deplac_y = mat.OffsetY;
v_deplac_z = mat.OffsetZ;
AfficherBoutonDeplacement();
}
Copyright 2013 Patrice REY
}
}
Un clic sur un bouton de translation permet d’incrémenter ou de décrémenter
les variables v_deplac_x, v_deplac_y et v_deplac_z, par pas de 0.1 en fonction de la
translation souhaitée (par exemple axe X côté positif ou côté négatif, etc.). La liste
v_liste_objet, de type List<UIElement3D>, contient les objets 3D ajoutés (cube et
cylindre). On parcourt cette liste pour trouver l’objet 3D sélectionné (par l’intermédiaire
CHAPITRE 6 L’interactivité des modèles 3D 243
4
248 Programmez des jeux vidéo 3D avec C#5 et WPF
qui représente une rotation en trois dimensions). Pour réaliser la rotation, la structure
Quaternion reçoit en paramètre un Vector3D représentant l’axe de rotation (Vector3D(1,
0, 0) pour l’axe X, Vector3D(0, 1, 0) pour l’axe Y, et Vector3D(0, 0, 1) pour l’axe Z), et une
valeur d’angle, de type double, pour représenter un angle de rotation en dégrés.
public class Cube : UIElement3D {
...
public void AppliquerRotationAxe(double angle_x,double angle_y,double angle_z) {
Quaternion qx = new Quaternion(new Vector3D(1, 0, 0), angle_x);
Quaternion qy = new Quaternion(new Vector3D(0, 1, 0), angle_y);
Quaternion qz = new Quaternion(new Vector3D(0, 0, 1), angle_z);
Transform3DGroup gp = new Transform3DGroup();
gp.Children.Add(new RotateTransform3D(new QuaternionRotation3D(qx)));
gp.Children.Add(new RotateTransform3D(new QuaternionRotation3D(qy)));
gp.Children.Add(new RotateTransform3D(new QuaternionRotation3D(qz)));
this.Transform = gp;
this.InvalidateModel();
}
...
}
Animation 3D
du
jeu vidéo
7
Construction du jeu du
Casse-Briques
Au sommaire de ce chapitre
• visualisation du résultat à obtenir pour une application sur Windows 7 et
pour une application sur une tablette tactile équipée avec Windows 8
• mise en place des objets 3D modélisés comme le plateau, les murs, la
raquette, la balle et les briques
• mise en application du rendu d’animation avec la classe CompositionTarget
• gérer le déplacement de la raquette et de la balle
• détection des collisions entre la balle et les autres objets 3D comme la
raquette, les briques et les murs
• mise en place de l’affichage du temps et du score
• gérer la fin de jeu
252 Programmez des jeux vidéo 3D avec C#5 et WPF
Le but de ce chapitre consiste à construire le jeu du Casse-Briques. Les différents objets
3D sont modélisés et ils sont ajoutés à la scène 3D. Puis, par l’intermédiaire d’un rendu
de type image par image, le jeu est animé pour permettre à l’utilisateur de jouer avec
une raquette déplaçable horizontalement, qui renvoie une balle pour détruire des
briques qui sont positionnées sur le plan du jeu, et tout cela dans un environnement
3D.
Pour permettre une meilleure utilisation à la fois sur un ordinateur de bureau et sur
une tablette tactile, le jeu va être réalisé pour remplir le bureau d’une tablette tactile
de 10.1 pouces (soit une résolution d’écran de 1280 par 800 pixels).
Avec une seule solution de projet, nous réalisons une application de jeu qui fonctionne
à la fois sur un ordinateur de bureau équipé d’un Windows 7 (figure 7.1) et sur une
tablette tactile de 10.1 pouces, équipée de Windows 8 (figure 7.2).
La tablette tactile utilisée ici est une tablette ASUS EP121, dotée d’un processeur Core
i3, avec une RAM de 2Go, et un système d’exploitation Windows 8 (en mode 64 bits).
Son écran de 10.1 pouces possède une résolution écran de 1280 par 800 pixels.
Comme vous pouvez le constater sur la figure 7.2, le rendu 2D de la scène 3D (par
l’intermédiaire du Viewport3D) occupe tout l’espace disponible sur le bureau de la
tablette tactile. Et pour l’application de jeu sur l’ordinateur de bureau (figure 7.1), on
définit une fenêtre de taille calculée pour remplir le même espace que sur un écran
de 10.1 pouces (celui de la tablette). La solution de projet CasseBrique3D.sln se trouve
dans le dossier 02_VS2012_3d_c5_wpf45_jeu/CasseBrique3D du code en téléchargement
gratuit sur www.reypatrice.fr.
Le jeu est composé d’objets 3D que l’on modélise au préalable (comme on l’a vu dans
la première partie du livre), puis on ajoute ces objets 3D à la scène 3D et on les anime
pour réaliser le jeu. L’animation consiste à donner la possibilité à la balle de se déplacer
dans l’espace 3D. La trajectoire de cette balle va être modifiée lorsqu’elle rencontrera
les murs de droite, de gauche et du fond. Sa trajectoire changera aussi lorsqu’elle tapera
Copyright 2013 Patrice REY
contre la raquette déplacée par l’utilisateur, et bien évidemment contre les briques
qu’elle détruira. En cours de jeu, un panneau permettra d’afficher le nombre de points
obtenus en fonction des briques cassées, ainsi qu’un indicateur de temps permettant
de connaître le temps écoulé jusqu’à la fin du jeu.
Pour percevoir l’ensemble de ces déplacements et autres effets, l’animation du jeu
consiste aussi à effectuer un rendu image par image, avec une cadence généralement
constatée de 60 images par seconde.
FIGURE
7.1
FIGURE
7.2
CHAPITRE 7 Construction du jeu du Casse-Briques 255
Commençons par définir la surface disponible pour le jeu sachant qu’il doit occuper
au moins la surface maximale d’un écran 10.1 pouces d’une tablette tactile. La classe
Screen représente le périphérique graphique de visualisation c’est-à-dire l’écran, qu’il
soit unique ou multiple (visualisation du bureau sur deux moniteurs par exemple).
Pour utiliser un objet Screen, il faut ajouter une référence à System.Windows.Forms.dll
(dans le dossier References) et une instruction using System.Windows.Forms pour son
utilisation. La figure 7.3 visualise l’arbre d’héritage de la classe Screen.
FIGURE 7.3
256 Programmez des jeux vidéo 3D avec C#5 et WPF
La classe Screen expose les propriétés suivantes:
• la propriété booléenne Primary permet d’obtenir une valeur pour savoir si l’écran
de visualisation est l’écran principal,
• la propriété DeviceName donne le nom associé à l’écran principal,
• la propriété Bounds permet d’obtenir la taille complète de l’écran en pixels (largeur
et hauteur),
• la propriété BitsPerPixel permet d’obtenir le nombre de bits pour la représentation
de la couleur d’un pixel,
• la propriété WorkingArea permet d’obtenir la surface utile du bureau c’est-à-dire la
surface ne contenant pas la barre des tâches et d’éventuelles fenêtres flottantes.
La méthode statique Screen.PrimaryScreen permet d’obtenir l’écran principal de
visualisation, et la méthode statique Screen.AllScreens permet d’obtenir un tableau
référençant tous les écrans disponibles du système.
On définit une classe Utils et on ajoute les méthodes statiques Utils.LargeurBureau
et Utils.HauteurBureau qui retournent respectivement la largeur et la hauteur de la
surface utile du bureau. Ces deux méthodes statiques s’appuient sur la propriété
WorkingArea de l’objet Screen.
public class Utils {
//trouver la largeur utile du bureau
public static int LargeurBureau() {
Rectangle rect_bureau= Screen.PrimaryScreen.WorkingArea;
return rect_bureau.Width;
}
//trouver la hauteur utile du bureau
public static int HauteurBureau() {
Rectangle rect_bureau = Screen.PrimaryScreen.WorkingArea;
return rect_bureau.Height;
}
}//end class
L’écran 10.1 pouces de la tablette tactile offre une résolution de 1280 par 800 pixels
(propriété Bounds de l’objet Screen). La hauteur de la barre des tâches est de 40 pixels
sous Windows 8 (même hauteur sous Windows 7). Par conséquent la surface utile du
bureau est de 1280 par 760 pixels.
Copyright 2013 Patrice REY
760 pixels
1280 pixels
760 pixels
1280 pixels
Comme on l’a vu dans la première partie du livre, un objet Viewport3D sert à effectuer
un rendu 2D d’une scène 3D. On insère l’objet Viewport3D dans un contrôle Border,
d’épaisseur 1 pixel et de couleur noire, car cela permet d’ajouter un fond au Viewport3D
par transparence. Ici, au lieu d’utiliser un fond bleu clair, on va peindre le fond du
Border par une image dont le fichier graphique représente un ciel avec des nuages
(image fond_ciel_4.jpg ajoutée au dossier contenu/image_texture). Pour que le contrôle
Border s’étende sur toute la surface utile, on le positionne dans un contrôle Grid avec
une seule cellule. La figure 7.4 visualise ce que l’on obtient dans le concepteur de vue
XAML.
FIGURE 7.4
<Grid Background=ˈ#FFD6F0FFˈ>
<Grid.RowDefinitions>
<RowDefinition Height=ˈ*ˈ/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=ˈ*ˈ/>
</Grid.ColumnDefinitions>
<!-- 3d -->
<Border BorderBrush=ˈBlackˈ BorderThickness=ˈ1ˈ MouseMove=ˈBorder_MouseMoveˈ>
<Border.Background>
<ImageBrush ImageSource=ˈcontenu/image_texture/fond_ciel_4.jpgˈ/>
</Border.Background>
<!-- zone de rendu 3d -->
<Viewport3D x:Name=ˈx_viewportˈ ClipToBounds=ˈTrueˈ>
...
</Viewport3D>
</Border>
...
</Grid>
Une caméra, de type perspective, est positionnée au point de coordonnées (0,2,6) par
la propriété Position, fixant l’origine par une propriété LookDirection (0,-2,-6), avec un
champs de vue de 70° (propriété FieldOfView), et une orientation de caméra vers le
haut (propriété UpDirection). Le volume de vue s’étend en distance de 0 (propriété
NearPlaneDistance) à 100 (propriété FarPlaneDistance).
<!-- cameras -->
<Viewport3D.Camera>
<PerspectiveCamera x:Name=ˈx_cameraˈ Position=ˈ0,2,6ˈ LookDirection=ˈ0,-2,-6ˈ
FieldOfView=ˈ70ˈ UpDirection=ˈ0 1 0ˈ NearPlaneDistance=ˈ0ˈ
FarPlaneDistance=ˈ100ˈ></PerspectiveCamera>
</Viewport3D.Camera>
Le contenu du Viewport3D est représenté par un objet ContainerUIElement3D intitulé
x_conteneur_3d. On positionne un éclairage général, de type AmbientLight, émettant
une lumière blanche (propriété Color fixée à White).
<ContainerUIElement3D x:Name=ˈx_conteneur_3dˈ>
<ModelUIElement3D x:Name=ˈx_lumiereˈ>
<AmbientLight Color=ˈWhiteˈ></AmbientLight>
</ModelUIElement3D>
...
</ContainerUIElement3D>
De façon à faciliter le positionnement des objets 3D dans le concepteur de vue XAML,
on va ajouter un repère de coordonnées 3D. Ce repère sera caché bien évidemment lors
de l’exécution du jeu. On procède ainsi car les objets 3D modélisés vont être ajoutés
directement en XAML, avec leur personnalisation au travers de leurs propriétés de
260 Programmez des jeux vidéo 3D avec C#5 et WPF
dépendances. La figure 7.5 visualise l’ajout d’un repère de coordonnées 3D dans le
concepteur XAML (repère 1) et avec la possibilité de personnaliser les propriétés de
dépendances (repère 2).
Dans le dossier modele3d, on ajoute une classe TRepere qui hérite de UIElement3D.
Cette classe TRepere expose les propriétés suivantes:
• P_Modele, de type Model3D, représente le modèle TRepere avec ses fonctionnalités,
• P_LongueurDemiAxe, de type double, représente la longueur d’un demi-axe à
appliquer pour les 3 axes (valeur par défaut 1d),
• P_EpaisseurAxe, de type double, représente l’épaisseur de la boite qui modélise
l’axe (valeur par défaut 0.01d),
• P_CouleurAxeX, de type Color, représente la couleur à appliquer pour matérialiser
l’axe X (valeur par défaut Red),
• P_CouleurAxeY, de type Color, représente la couleur à appliquer pour matérialiser
l’axe Y (valeur par défaut Green),
• P_CouleurAxeZ, de type Color, représente la couleur à appliquer pour matérialiser
l’axe Z (valeur par défaut Blue).
Ces propriétés de dépendances peuvent être personnalisées directement en XAML
(repère 1 et 2 de la figure 7.5). A noter que pour importer un objet TRepere appartenant
à l’espace de noms CasseBrique3D.modele3d, il faut ajouter une référence xmlns à cet
espace de noms, que l’on préfixe par un identifiant arbitraire comme modele3d par
exemple (xmlns:modele3d = «clr-namespace:CasseBrique3D.modele3d»).
FIGURE 7.5
1
CHAPITRE 7 Construction du jeu du Casse-Briques 261
namespace CasseBrique3D.modele3d {
public class TRepere : UIElement3D {
//la propriete Modele pour le repere
private static readonly DependencyProperty ProprieteModele =
DependencyProperty.Register(«P_Modele»,
typeof(Model3D),
typeof(TRepere),
new PropertyMetadata(PM_Modele));
//la propriete LongueurDemiAxe
private static readonly DependencyProperty ProprieteLongueurDemiAxe =
DependencyProperty.Register(«P_LongueurDemiAxe»,
typeof(double),
typeof(TRepere),
new PropertyMetadata(1.0, PM_LongueurDemiAxe));
//la propriete EpaisseurAxe
private static readonly DependencyProperty ProprieteEpaisseurAxe =
DependencyProperty.Register(«P_EpaisseurAxe»,
typeof(double),
typeof(TRepere),
new PropertyMetadata(0.01, PM_EpaisseurAxe));
//la propriete CouleurAxeX
private static readonly DependencyProperty ProprieteCouleurAxeX =
DependencyProperty.Register(«P_CouleurAxeX»,
typeof(Color),
typeof(TRepere),
new PropertyMetadata(Colors.Red, PM_CouleurAxeX));
//la propriete CouleurAxeY
private static readonly DependencyProperty ProprieteCouleurAxeY =
DependencyProperty.Register(«P_CouleurAxeY»,
typeof(Color),
typeof(TRepere),
new PropertyMetadata(Colors.Green, PM_CouleurAxeY));
//la propriete CouleurAxeZ
private static readonly DependencyProperty ProprieteCouleurAxeZ =
DependencyProperty.Register(«P_CouleurAxeZ»,
typeof(Color),
typeof(TRepere),
new PropertyMetadata(Colors.Blue, PM_CouleurAxeZ));
//redefinition de la methode de rendu
protected override void OnUpdateModel() {
Model3DGroup groupe_axe = new Model3DGroup();
groupe_axe.Children.Add(GenererMaillage(P_LongueurDemiAxe, «x»));
groupe_axe.Children.Add(GenererMaillage(P_LongueurDemiAxe, «y»));
groupe_axe.Children.Add(GenererMaillage(P_LongueurDemiAxe, «z»));
P_Modele = groupe_axe;
}
...
}
}
262 Programmez des jeux vidéo 3D avec C#5 et WPF
Le plateau du jeu peut être matérialisé par exemple comme une simple face
rectangulaire, dont le centre de la face est positionné à l’origine du repère de
coordonnées (donc avec une coordonnée de valeur 0 selon l’axe Y), et dont on fixe
les dimensions selon les axes X et Z. Pour augmenter le réalisme de ce plateau, on
applique une image de texture à la face rectangulaire.
Y
P_LargeurXNeg P_LargeurXPos
P_ProfondeurZNeg
X
P_ProfondeurZPos
Z
Pour représenter le plateau, on déclare une classe TSol qui hérite de UIElement3D.
Cette classe TSol expose les propriétés suivantes:
• P_Modele, de type Model3D, représente le modèle TSol avec ses fonctionnalités,
• P_LargeurXPos et P_LargeurXNeg, de type double, représentent les distances des
côtés du plateau suivant l’axe X (valeur par défaut 1d),
• P_ProfondeurZPos et P_ProfondeurZNeg, de type double, représentent les distances
des côtés du plateau suivant l’axe Z (valeur par défaut 1d),
• P_CouleurUnie, de type Color, représente la couleur du plateau quand aucune
texture n’est appliquée (valeur par défaut Aqua),
• P_Texture, de type Material, représente la texture à appliquer sur la face du plateau
Copyright 2013 Patrice REY
P_LargeurXPos=ˈ2ˈ P_LargeurXNeg=ˈ2ˈ>
</modele3d:TSol>
Le repère 1 de la figure 7.6 visualise ce que l’on obtient dans le concepteur de vue XAML.
Le plateau est de couleur unie, celle choisie et fixée dans la propriété P_CouleurUnie.
Dans la méthode redéfinie OnUpdateModel, méthode qui dessine la modélisation 3D
de l’objet TSol (par héritage de UIElement3D), on ajoute une instruction conditionnelle
pour permettre l’application d’une texture sur la face rectangulaire si la propriété P_
Texture est différente de la valeur null. Le repère 2 de la figure 7.6 visualise le résultat
obtenu avec l’application d’une texture par l’intermédiaire d’une ressource image
(contenu/image_texture/texture_sol_1.png).
FIGURE 7.6
2
264 Programmez des jeux vidéo 3D avec C#5 et WPF
//redefinition de la methode de rendu
protected override void OnUpdateModel() {
GeometryModel3D geo = new GeometryModel3D();
geo.Geometry = Maillage(this.P_LargeurXPos, this.P_LargeurXNeg,
this.P_ProfondeurZPos, this.P_ProfondeurZNeg);
if (this.P_Texture == null) {
geo.Material = new DiffuseMaterial(new SolidColorBrush(this.P_CouleurUnie));
}
else {
geo.Material = this.P_Texture;
}
geo.BackMaterial = new DiffuseMaterial(new SolidColorBrush(Colors.Yellow));
P_Modele = geo;
}
Pour ajouter une texture, on affecte un objet de type Material à la propriété P_Texture
(en écrivant <modele3d:TSol.P_Texture>). On ajoute un objet de type DiffuseMaterial
(qui hérite de Material) et on applique à sa propriété Brush un objet ImageBrush,
dont la propriété ImageSource de ImageBrush référence une image texture_sol_1.png
stockée dans le dossier contenu/image_texture.
<!-- sol du jeu -->
<modele3d:TSol x:Name=ˈx_solˈ P_ProfondeurZPos=ˈ3.7ˈ P_ProfondeurZNeg=ˈ3.4ˈ
P_LargeurXPos=ˈ2ˈ P_LargeurXNeg=ˈ2ˈ>
<modele3d:TSol.P_Texture>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource=ˈcontenu/image_texture/texture_sol_1.pngˈ >
</ImageBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</modele3d:TSol.P_Texture>
</modele3d:TSol>
Il faut maintenant poser des bordures autour du plateau du jeu pour limiter l’espace
de jeu. On commence par ajouter un objet TAfficheMurale qui représente une affiche
murale et qui est positionnée au fond du plateau du jeu. C’est en fait un mur qui
permettra de renvoyer la balle quand cette dernière le touchera.
Cette affiche est constituée par le maillage simple d’une surface rectangulaire, aux
Copyright 2013 Patrice REY
dimensions choisies, sur laquelle on appliquera une texture sous forme d’image
graphique. De façon à positionner plus facilement cet objet dans l’espace 3D, on va
ajouter un point de référence Pr, qui est en sorte un point à partir duquel l’objet
sera modélisé. Si ce point est fixé aux coordonnées de l’origine, alors Pr se trouvera à
l’origine du repère et l’objet sera modélisé en rapport avec ce point de référence. De
cette façon, on peut directement modéliser l’objet en fixant simplement un point de
référence choisi.
CHAPITRE 7 Construction du jeu du Casse-Briques 265
P_Largeur
Y
TAfficheMurale x_affiche_murale_fond
P_Hauteur
Pr
Pr : point de référence
X
TSol x_sol
2
Copyright 2013 Patrice REY
</modele3d:TAfficheMurale.P_PointReference>
<modele3d:TAfficheMurale.P_Texture>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource=ˈcontenu/image_texture/texture_affiche_1.pngˈ />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</modele3d:TAfficheMurale.P_Texture>
</modele3d:TAfficheMurale>
Pour terminer la limitation de la surface du jeu par des objets fixes, on va ajouter à
droite et à gauche un mur. Pour cela, on va modéliser un cube particulier pour lequel
on pourra lui donner une largeur, une hauteur, une profondeur et une texture pour
chaque face.
On déclare une classe TCubeDetailFace qui hérite de UIElement3D et qui expose les
propriétés suivantes:
• P_Modele, de type Model3D, représente le modèle TCubeDetailFace avec ses
fonctionnalités,
• P_PointReference, de type Point3D, représente le point à partir duquel l’affiche est
modélisée (valeur par défaut aux coordonnées 0,0,0).,
• P_LargeurX, de type double, représente la largeur du cube suivant l’axe X (valeur
par défaut 1d),
• P_HauteurY, de type double, représente la hauteur du cube suivant l’axe Y (valeur
par défaut 1d),
• P_ProfondeurZ, de type double, représente la profondeur du cube suivant l’axe Z
(valeur par défaut 1d),
• P_CouleurUnie, de type Color, représente la couleur du cube sur toutes ses faces
quand aucune texture n’est appliquée (valeur par défaut Red),
• les propriétés P_TextureFaceAvant, P_TextureFaceDroite, P_TextureFaceGauche, P_
TextureFaceArriere, P_TextureFaceDessus, P_TextureFaceDessous, de type Material,
représentent les textures à appliquer sur les faces du cube respectivement (valeur
par défaut null).
En XAML (figure 7.8), on ajoute un mur côté gauche nommé x_mur_gauche et un
mur côté droit nommé x_mur_droit. On personnalise le point de référence pour la
modélisation, les tailles des murs selon les 3 axes et les textures à appliquer en fonction
des faces choisies.
Pour le mur droit, on ajoutera une texture sur la face de gauche et la face du dessus.
Pour le mur gauche, on ajoutera une texture sur la face de droite et la face du dessus.
Les autres faces resteront avec la couleur par défaut (Red) car elles ne seront pas
visibles.
268 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 7.8
1 1
Y TAfficheMurale x_affiche_murale_fond
TCubeDetailFace
x_mur_gauche
TCubeDetailFace
x_mur_droit
TSol x_sol
Z Copyright 2013 Patrice REY
P_ProfondeurZ
P_HauteurY
Pr
Pr : point de référence
P_LargeurX
CHAPITRE 7 Construction du jeu du Casse-Briques 269
1 1
1
272 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 7.10
1
1
Maintenant nous allons ajouter la raquette. La raquette est l’objet qui sert à renvoyer
la balle. Pour cela, on va modéliser une raquette en forme de cube allongé pour lequel
on applique une texture unique pour le décorer. On déclare une classe TCube et on lui
ajoute les propriétés de dépendances suivantes:
• P_Modele, de type Model3D, représente le modèle TCube avec ses fonctionnalités,
• P_PointReference, de type Point3D, représente le point à partir duquel le cube est
modélisé (valeur par défaut aux coordonnées 0,0,0).,
• P_LargeurX, de type double, représente la largeur du cube suivant l’axe X (valeur
par défaut 1d),
• P_HauteurY, de type double, représente la hauteur du cube suivant l’axe Y (valeur
par défaut 1d),
• P_ProfondeurZ, de type double, représente la profondeur du cube suivant l’axe Z
(valeur par défaut 1d),
Copyright 2013 Patrice REY
• P_CouleurUnie, de type Color, représente la couleur du cube sur toutes ses faces
quand aucune texture n’est appliquée (valeur par défaut Red),
• P_Texture, de type Material, représente la texture à appliquer sur le cube dans son
intégralité (valeur par défaut null).
D’autres fonctionnalités seront ajoutées par la suite pour l’animation de la raquette. La
figure 7.11 visualise la raquette que l’on positionne dans la partie basse du plateau du
CHAPITRE 7 Construction du jeu du Casse-Briques 273
1 1
Quand le jeu démarre, l’utilisateur doit pouvoir bouger la raquette pour renvoyer la
balle. Intuitivement, on perçoit que, pour exécuter un déplacement de la raquette en
fonction d’un événement déclenché par la souris, il faudra effectuer des translations
dans l’espace 3D. Or, pour que le mouvement de la raquette soit visible à l’écran, il va
falloir réaliser un rendu image par image. De cette façon, dès que l’on effectuera une
translation de la raquette, celle-ci sera vue car on aura un rafraîchissement de l’écran
de jeu qui permettra de visualiser la position de la raquette en temps réel.
La classe abstraite CompositionTarget, dans l’espace de noms System.Windows.
274 Programmez des jeux vidéo 3D avec C#5 et WPF
Media, permet d’effectuer un rendu image par image de la surface de visualisation
de l’application. La figure 7.12 montre l’arbre d’héritage de la classe abstraite
CompositionTarget. L’objet CompositionTarget fournit la capacité à créer des
animations personnalisées qui sont basées sur un rendu image par image. Généralement
on constate que la vitesse de ce rendu image par image est de l’ordre de 60 images par
seconde, ce qui est très correct et qui apporte un excellent réalisme au jeu.
La classe CompositionTarget est une classe abstraite, et par conséquent non
instanciable. Elle possède une méthode statique CompositionTarget.Rendering pour
accéder à l’événement Rendering. Cet événement se produit juste avant que les objets
de l’arbre visuel soient rendus. Donc l’événement Rendering permet d’effectuer le
rendu de la scène (c’est la boucle de rendu).
FIGURE 7.12
2
276 Programmez des jeux vidéo 3D avec C#5 et WPF
La mesure du temps dans un jeu est essentielle. Il faut pouvoir démarrer un jeu
avec un relevé d’horloge, il faut pouvoir mesurer le temps écoulé entre deux relevés
d’horloge, et quand on veut mettre un jeu en pause, il faut pouvoir suspendre l’horloge
et l’animation du jeu pour pouvoir ensuite la reprendre. Nous allons utiliser pour cela
la classe Stopwatch qui se trouve dans l’espace de noms System.Diagnostics. La classe
Stopwatch fournit un ensemble de méthodes et de propriétés utilisables pour des
mesures pertinentes du temps. La figure 7.14 visualise l’arbre d’héritage de la classe
Stopwatch.
FIGURE 7.14
en millisecondes,
• la méthode Reset stoppe l’horloge et remet à zéro le temps écoulé (propriété
Elapsed).
Sur la surface de l’application, en haut et à gauche (figure 7.15), on installe un
indicateur de temps en incrustation par l’intermédiaire d’un Grid x_grid_temps. Cette
grille comporte un TextBlock x_text_horloge pour indiquer le temps écoulé (au format
HH:MM:SS.MS), un TextBlock x_text_nb_img pour indiquer le nombre d’images rendues
par la boucle de rendu (c’est-à-dire le nombre de fois où l’événement Rendering a été
effectué), et un TextBlock x_text_fps pour indiquer le nombre d’image par seconde (la
cadence correspondant au nombre d’images rendues pour une unité de temps de une
seconde).
FIGURE 7.15
TextBlock x_text_horloge
TextBlock x_text_nb_img
TextBlock x_text_fps
Nous allons utiliser le maillage d’une sphère comme représentation de la balle. Pour
cela on déclare une classe TSphere, qui hérite de UIElement3D, dans l’espace de
noms CasseBrique3D.modele3d. L’objet TSphere expose principalement les propriétés
suivantes:
• P_Modele, de type Model3D, représente le modèle TSphere avec ses fonctionnalités,
• P_PointReference, de type Point3D, représente le point à partir duquel la sphère est
modélisée (valeur par défaut aux coordonnées 0,0,0),
• P_Rayon, de type double, représente le rayon de la sphère (valeur par défaut 1d),
• P_DivHori, de type int, représente le nombre de division horizontalement pour les
facettes de la sphère (valeur par défaut 40),
• P_DivVerti, de type int, représente le nombre de division verticalement pour les
facettes de la sphère (valeur par défaut 40),
• P_CouleurUnie, de type Color, représente la couleur appliquée sur les facettes qui
composent la sphère (valeur par défaut Bisque),
• P_Texture, de type Material, représente la texture à appliquer sur l’intégralité de la
sphère (valeur par défaut null).
Le point de référence utilisé pour la modélisation de la sphère est son centre. On utilise
la même technique de maillage que celle vue dans la première partie du livre.
//redefinition de la methode de rendu
protected override void OnUpdateModel() {
//face exterieure sphere
GeometryModel3D gp_geo_sphere = new GeometryModel3D();
gp_geo_sphere.Geometry = MaillageSphereFace(this.P_PointReference, this.P_Rayon,
this.P_DivHori, this.P_DivVerti);
if (this.P_Texture != null) {
gp_geo_sphere.Material = this.P_Texture;
}
else {
gp_geo_sphere.Material = new DiffuseMaterial(
Copyright 2013 Patrice REY
new SolidColorBrush(this.P_CouleurUnie));
}
gp_geo_sphere.BackMaterial = new DiffuseMaterial(
new SolidColorBrush(Colors.Yellow));
//
P_Modele = gp_geo_sphere;
...
}
CHAPITRE 7 Construction du jeu du Casse-Briques 281
Pour ajouter une balle x_balle (figure 7.17), de type TSphere, dont le rayon est de 0.1,
le nombre de division horizontal et vertical est de 20, et dont le point de référence
(centre de la sphère) est de coordonnées (0,0.1,3), on écrira en XAML:
<!-- balle sphere -->
<modele3d:TSphere x:Name=ˈx_balleˈ P_Rayon=ˈ0.1ˈ P_DivHori=ˈ20ˈ P_DivVerti=ˈ20ˈ>
<modele3d:TSphere.P_PointReference>
<Point3D X=ˈ0ˈ Y=ˈ0.1ˈ Z=ˈ3ˈ></Point3D>
</modele3d:TSphere.P_PointReference>
<modele3d:TSphere.P_Texture>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource=ˈcontenu/image_texture/texture_sphere_1.pngˈ />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</modele3d:TSphere.P_Texture>
</modele3d:TSphere>
FIGURE 7.17
1
1
this.InvalidateModel();
}
...
}
En lançant le jeu, la balle se déplace vers le fond du plateau en fonction du temps et
cela indéfiniment pour l’instant.
Quand la balle arrivera au fond du plateau, elle tombera sur le mur du fond (x_affiche_
murale_fond de type TAfficheMurale). Une collision doit donc se produire et il faut
gérer cette collision pour déterminer les nouvelles valeurs de v_vitesse_taux_x et v_
vitesse_taux_z qui sont affectées à v_vitesse_x et v_vitesse_z.
Trois cas peuvent se présenter lors de cette collision. Dans le premier cas, la direction
de la balle est une direction de la gauche vers la droite, cela veut dire que la balle se
dirige avec une direction vers l’axe X positif et l’axe Z négatif. Le résultat de la collision
sera d’inverser le mouvement en redirigeant la balle vers l’axe Z positif. La variable
v_vitesse_taux_x sera inchangée et v_vitesse_taux_z sera inversée.
TAfficheMurale
x_affiche_murale_fond v_vitesse_taux_x = v_vitesse_taux_x
v_vitesse_taux_z = -v_vitesse_taux_z
TSol x_sol
Z
284 Programmez des jeux vidéo 3D avec C#5 et WPF
Dans le deuxième cas, la direction de la balle est une direction de la droite vers la
gauche, cela veut dire que la balle se dirige avec une direction vers l’axe X négatif et
l’axe Z négatif. Le résultat de la collision sera d’inverser le mouvement en redirigeant la
balle vers l’axe Z positif. La variable v_vitesse_taux_x sera inchangée et v_vitesse_taux_z
sera inversée.
TAfficheMurale
x_affiche_murale_fond v_vitesse_taux_x = v_vitesse_taux_x
v_vitesse_taux_z = -v_vitesse_taux_z
TSol x_sol
Z
Dans le troisième cas, la direction de la balle est une direction parallèle à l’axe Z, cela
veut dire que la balle se dirige avec une direction vers l’axe Z négatif. Le résultat de la
collision sera d’inverser le mouvement en redirigeant la balle vers l’axe Z positif. La
variable v_vitesse_taux_x sera inchangée et v_vitesse_taux_z sera inversée.
TAfficheMurale
x_affiche_murale_fond v_vitesse_taux_x = v_vitesse_taux_x
v_vitesse_taux_z = -v_vitesse_taux_z
TSol x_sol
Location de
type Point3D
SizeY
SizeZ
SizeX
286 Programmez des jeux vidéo 3D avec C#5 et WPF
De ce fait, pour la balle de type TSphere, on ajoute un champ v_boite_englobante,
de type Rect3D, et une propriété P_BoiteEnglobante pour fixer et obtenir ce Rect3D.
Dans la méthode de rendu du modèle, on affecte à v_boite_englobante la valeur de la
propriété P_Modele.Bounds.
//champs
private Rect3D v_boite_englobante;
//proprietes
public Rect3D P_BoiteEnglobante {
get { return v_boite_englobante; }
private set { v_boite_englobante = value; }
}
//redefinition de la methode de rendu
protected override void OnUpdateModel() {
//face exterieure sphere
GeometryModel3D gp_geo_sphere = new GeometryModel3D();
...
//
P_Modele = gp_geo_sphere;
v_boite_englobante = P_Modele.Bounds;
}
On procède de la même manière pour TAfficheMurale ainsi que tous les autres modèles
utilisés (TCube, TCone, TCylindre, TCubeDetailFace, etc.).
Quand on applique une translation à la sphère, il ne faut pas oublier de recalculer
les valeurs de la boite englobante. En effet, la boite englobante de type Rect3D est
centrée sur le modèle mais ne tient pas compte des transformations appliquées. D’où
un nouveau calcul à effectuer qui consiste à relever le Point3D de la boite englobante
par sa propriété Location, puis d’actualiser les déplacements de translation par la
méthode Point3D.Offset, enfin d’affecter ce nouveau Point3D à la propriété Location
de la boite englobante.
//appliquer une translation a la sphere
public void AppliquerTranslation(double suiv_x, double suiv_y, double suiv_z) {
this.Transform = new TranslateTransform3D(suiv_x, suiv_y, suiv_z);
this.InvalidateModel();
Point3D pt3d = v_boite_englobante.Location;
pt3d.Offset(suiv_x, suiv_y, suiv_z);
Copyright 2013 Patrice REY
v_boite_englobante.Location = pt3d;
}
Maintenant que nos objets possèdent une boite englobante, la détection de collision
consiste à voir si la méthode Rect3D.IntersectWith retourne un Rect3D vide ou non vide.
//determiner la possibilité d’une collision avec un TAfficheMurale
public bool CollisionDetecter(TAfficheMurale obj_affiche_murale) {
bool collision = false;
CHAPITRE 7 Construction du jeu du Casse-Briques 287
TSol x_sol
TAfficheMurale
TCubeDetailFace x_affiche_murale_fond
x_mur_gauche
v_vitesse_taux_x = -v_vitesse_taux_x
v_vitesse_taux_z = v_vitesse_taux_z
TSol x_sol
Z
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
if (v_etat_jeu == EtatJeu.en_cours) {
x_raquette.AppliquerTranslation(v_deplac / 320d, 0, 0);
v_vitesse_x += v_vitesse_taux_x;
v_vitesse_z += v_vitesse_taux_z;
x_balle.AppliquerTranslation(v_vitesse_x, 0, v_vitesse_z);
if (x_balle.CollisionDetecter(x_affiche_murale_fond) == true) {
288 Programmez des jeux vidéo 3D avec C#5 et WPF
v_vitesse_taux_z = -v_vitesse_taux_z;
}
if (x_balle.CollisionDetecter(x_mur_droit) == true) {
v_vitesse_taux_x = -v_vitesse_taux_x;
}
if (x_balle.CollisionDetecter(x_mur_gauche) == true) {
v_vitesse_taux_x = -v_vitesse_taux_x;
}
}
...
}
//determiner la possibilité d’une collision avec un TCubeDetailFace
public bool CollisionDetecter(TCubeDetailFace obj_cube_detail_face) {
bool collision = false;
Rect3D rect3d_cube_detail_face = obj_cube_detail_face.P_BoiteEnglobante;
if (v_boite_englobante.IntersectsWith(rect3d_cube_detail_face) == true) {
collision = true;
}
return collision;
}
3 - Les collisions
Dans un jeu, la détection des collisions est une notion essentielle. On a vu dans le
paragraphe précédent une détection des collisions entre la balle et les murs (x_mur_
gauche et x_mur_droit de type TCubeDetailFace, et x_affiche_murale_fond de type
TAfficheMurale). Ces détections de collisions étaient simples puisqu’elles consistaient
à réfléchir la direction de parcours de la balle.
Maintenant nous allons aborder la détection des collisions, dans un premier temps,
entre la balle et la raquette, et dans un deuxième temps, entre la balle et les briques.
Ces détections de collisions seront basées sur un principe plus évolué pour donner un
intérêt certain au jeu.
2
290 Programmez des jeux vidéo 3D avec C#5 et WPF
Entre ces deux cas spécifiques, la balle aura une orientation de direction qui sera
proportionnelle à l’emplacement d’impact de la balle sur la raquette. Pour l’instant,
on a défini une variable v_vitesse_taux égale à 0.05d et qui correspond à une vitesse
moyenne correcte de déplacement de la balle sur le plateau. De plus, en démarrant le
jeu, on a initialisé l’orientation de cette direction par v_vitesse_taux_x = 0 et v_vitesse_
taux_z = -v_vitesse_taux, ce qui est équivalent à une orientation parallèle à l’axe Z et
orientée vers l’axe Z négatif. D’où, au début, la balle se dirige vers le fond du plateau.
Sur le schèma ci-dessous, on projette en 2D le plan OXZ qui est le plan de déplacement
de la balle. La flèche noire représente l’orientation à donner à la balle, et sa longueur
est égale à 0.05 (valeur de v_vitesse_taux). L’angle entre la flèche et l’axe X est de 90°.
On peut donc en déduire les projections dx et dz qui représentent respectivement les
projections suivant l’axe X et suivant l’axe Z. On a donc les relations suivantes (avec
d=0.05 correspondant à la longueur de la flèche noire):
cos(90°) = dx / d soit dx = d * cos(90°) = 0
et
sin(90°) = -dz / d soit dz = -d * sin(90°) = -0.05
0.06
P
0.05
cos(90°) = dx / d
0.04 soit
dx = d * cos(90°)
et
0.03 sin(90°) = -dz / d
dz = -0.05 soit
d = 0.05
dz = -d * sin(90°)
0.02
angle = 90°
0.01
Copyright 2013 Patrice REY
O
X
dx = 0 0.01 0.02 0.03 0.04 0.05 0.06
Z
Si l’on souhaite donner une orientation différente à la balle par modification de l’angle,
il faut que le calcul de cette orientation donne l’impression que la balle possède la
CHAPITRE 7 Construction du jeu du Casse-Briques 291
même vitesse. Intuitivement, on en déduit que pour donner une orientation spécifique
suivant un angle donné, il faudra calculer les projections dx et dz de la flèche noire de
longueur d=0.05 c’est-à-dire le long de l’arc de cercle de rayon d centré sur O.
Par exemple, pour donner une orientation de 45° (schéma ci-dessous), on obtiendra
les valeurs de projection dx = 0.035 et dz = -0.035 :
cos(45°) = dx / d soit dx = d * cos(45°) = 0.035
et
sin(45°) = -dz / d soit dz = -d * sin(45°) = -0.035
0.06
0.05
cos(45°) = dx / d
soit
0.04 dx = d * cos(45°)
P
et
sin(45°) = -dz / d
0.03 soit
dz = -d * sin(45°)
dz = -0.035 0.02
0.01
angle = 45°
X
O 0.01 0.02 0.03 0.04 0.05 0.06
dx = 0.035
Z
x = 0.035
z = -0.035
angle de 45°
292 Programmez des jeux vidéo 3D avec C#5 et WPF
Autre exemple, pour donner une orientation de 135°, on obtiendra les valeurs de
projection dx = -0.035 et dz = -0.035 :
cos(135°) = dx / d soit dx = d * cos(135°) = -0.035
et
sin(135°) = -dz / d soit dz = -d * sin(135°) = -0.035
0.06
0.05
0.04
P
0.03
dz = -0.035
0.02
angle
= 135°
0.01
X
O 0.01 0.02 0.03 0.04 0.05
dx = -0.035
Z
x = -0.035
z = -0.035
angle de 135°
Copyright 2013 Patrice REY
angle de 20°
angle de 160°
Dans la boucle du rendu, on ajoute un test pour voir s’il y a une collision entre les
boites englobantes de la raquette et de la balle, en utilisant la méthode TSphere.
CollisionDetecter qui reçoit en paramètre un TCube (type de la raquette).
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
if (v_etat_jeu == EtatJeu.en_cours) {
x_raquette.AppliquerTranslation(v_deplac / 320d, 0, 0);
v_vitesse_x += v_vitesse_taux_x;
v_vitesse_z += v_vitesse_taux_z;
x_balle.AppliquerTranslation(v_vitesse_x, 0, v_vitesse_z);
if (x_balle.CollisionDetecter(x_affiche_murale_fond) == true) {
v_vitesse_taux_z = -v_vitesse_taux_z;
}
if (x_balle.CollisionDetecter(x_mur_droit) == true) {
v_vitesse_taux_x = -v_vitesse_taux_x;
}
if (x_balle.CollisionDetecter(x_mur_gauche) == true) {
v_vitesse_taux_x = -v_vitesse_taux_x;
}
if (x_balle.CollisionDetecter(x_raquette) == true) {
...
}
}
...
}
Le détail de la méthode CollisionDetecter reste identique à ce que l’on a déjà vu
concernant l’intersection entre deux boites englobantes.
294 Programmez des jeux vidéo 3D avec C#5 et WPF
public class TSphere : UIElement3D {
//determiner la possibilité d’une collision avec un TCube
public bool CollisionDetecter(TCube obj_cube) {
bool collision = false;
Rect3D rect3d_obj_cube = obj_cube.P_BoiteEnglobante;
if (v_boite_englobante.IntersectsWith(rect3d_obj_cube) == true) {
collision = true;
}
return collision;
}
}
Si une collision s’est bien produite, il faut donc, dans un premier temps, apprécier la
position de la balle par rapport à la raquette selon l’axe X, et dans un second temps,
calculer l’angle à appliquer pour déterminer la direction de la balle. Pour cela on va
introduire la notion de point de gravité. Un point de gravité sera un point calculé qui
correspondra au centre du modèle. Le point de gravité ne sera rien d’autre que le
centre de la boite englobante.
On ajoute donc pour la balle x_balle (de type TSphere) et pour la raquette x_raquette (de
type TCube) un champ v_point_gravite de type Point3D et une propriété P_PointGravite
comme accesseurs.
public class TSphere : UIElement3D {
//champs
private Point3D v_point_gravite;
//proprietes
public Point3D P_PointGravite {
get { return v_point_gravite; }
private set { v_point_gravite = value; }
}
//redefinition de la methode de rendu
protected override void OnUpdateModel() {
//
P_Modele = gp_geo_sphere;
v_boite_englobante = P_Modele.Bounds;
v_point_gravite = CalculPointGraviteParBoite(v_boite_englobante);
}
//calculer le point de gravité a partir de la boite englobante
private Point3D CalculPointGraviteParBoite(Rect3D v_boite_englobante) {
Copyright 2013 Patrice REY
S R
R Gr
S
Gb
et 20° si la balle est du côté droit de la raquette, et un angle compris entre 90° et 160°
si la balle est du côté gauche de la raquette. A partir de angle_degree, on effectue la
conversion en radians pour obtenir l’angle en radians angle_radian. Et on calcule les
nouvelles directions selon X et selon Z à appliquer à la balle à savoir v_vitesse_taux_x
= v_vitesse_taux * Math.Cos(angle_radian) et v_vitesse_taux_z = -v_vitesse_taux * Math.
Sin(angle_radian). La figure 7.19 visualise les deux cas extrêmes et la cas du milieu.
CHAPITRE 7 Construction du jeu du Casse-Briques 297
FIGURE 7.19
coordonnée x de Gb x=0
z = -0.05
angle de 90°
coordonnée x de Gr
coordonnée x de Gb
x = -0.017
z = -0.046
angle de 160°
coordonnée x de Gr
coordonnée x de Gb
x = 0.017
z = -0.046
angle de 20°
coordonnée x de Gr
Le moment est venu d’ajouter des briques à détruire pour notre Casse-briques. Nous
allons modéliser la brique par un cube sur lequel on va appliquer une texture affichant
une valeur de points à gagner en cas de destruction. Le repère 1 de la figure 7.20
montre le plateau rempli avec 16 briques dans le concepteur XAML. Le repère 2 de la
figure 7.20 montre de plus près la texture appliquée à la brique, affichant une valeur
de points à gagner.
298 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 7.20
On déclare une classe TBriqueCube, qui hérite de UIElement3D, et qui expose les
propriétés suivantes:
Copyright 2013 Patrice REY
case ApportGains.val_150:
brique.P_Texture = new DiffuseMaterial(brique.v_liste_pinceau[2]);
break;
case ApportGains.val_200:
brique.P_Texture = new DiffuseMaterial(brique.v_liste_pinceau[3]);
break;
}
brique.InvalidateModel();
}
public ApportGains P_Gains {
get { return (ApportGains)GetValue(ProprieteGains); }
set { SetValue(ProprieteGains, value); }
}
Pour ajouter une brique en XAML avec un nombre de points voulu, il suffira
d’instancier une brique par <modele3d:TBriqueCube>, de lui donner des dimensions
voulues (P_LargeurX, P_Hauteur_Y et P_Profondeur_Z), de la positionner sur le plateau
par la propriété P_PointReference (<modele3d:TBriqueCube.P_PointReference>), et de
personnaliser son nombre de points par la propriété P_Gains. Par exemple pour une
brique rapportant 50 points, on écrira:
<modele3d:TBriqueCube x:Name=ˈx_brique_1_2ˈ P_LargeurX=ˈ0.20ˈ P_HauteurY=ˈ0.20ˈ
P_ProfondeurZ=ˈ0.20ˈ P_Gains=ˈval_050ˈ>
<modele3d:TBriqueCube.P_PointReference>
<Point3D X=ˈ-0.45ˈ Y=ˈ0ˈ Z=ˈ2ˈ></Point3D>
</modele3d:TBriqueCube.P_PointReference>
</modele3d:TBriqueCube>
Et par exemple pour une brique rapportant 200 points, on écrira:
<modele3d:TBriqueCube x:Name=ˈx_brique_1_2ˈ P_LargeurX=ˈ0.20ˈ P_HauteurY=ˈ0.20ˈ
P_ProfondeurZ=ˈ0.20ˈ P_Gains=ˈval_200ˈ>
<modele3d:TBriqueCube.P_PointReference>..</modele3d:TBriqueCube.P_PointReference>
</modele3d:TBriqueCube>
P_Gains = ApportGains.val_150 P_Gains = ApportGains.val_200
La collision entre une balle et une brique se fait en trois temps: en premier on détecte
avec quelle brique la balle est entrée en collision; en second on gère la direction à
donner à la balle en fonction de la face de la brique qui a été atteinte; en dernier on
retire la brique du plateau.
Pour détecter la possibilité d’une collision entre la balle et une brique, on parcourt la
liste v_liste_brique et on cherche si la méthode booléenne TSphere.CollisionDetecter
Copyright 2013 Patrice REY
retourne la valeur true pour une brique donnée. On ajoute donc une méthode
CollisionDetecter qui reçoit un objet TBriqueCube et qui retourne une valeur booléenne.
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
if (v_etat_jeu == EtatJeu.en_cours) {
x_raquette.AppliquerTranslation(v_deplac / 320d, 0, 0);
v_vitesse_x += v_vitesse_taux_x;
CHAPITRE 7 Construction du jeu du Casse-Briques 303
v_vitesse_z += v_vitesse_taux_z;
x_balle.AppliquerTranslation(v_vitesse_x, 0, v_vitesse_z);
...
//collision avec des briques
int index_collision = -1;
for (int xx = 0; xx < v_liste_brique.Count; xx++) {
modele3d.TBriqueCube brique = v_liste_brique[xx];
if (x_balle.CollisionDetecter(brique) == true) {
...
}
}
}
...
}
//determiner la possibilité d’une collision avec un TBriqueCube
public bool CollisionDetecter(TBriqueCube obj_brique) {
bool collision = false;
Rect3D rect3d_obj_brique = obj_brique.P_BoiteEnglobante;
if (v_boite_englobante.IntersectsWith(rect3d_obj_brique) == true) {
collision = true;
}
return collision;
}
Une brique est modélisée par un cube et comporte donc six faces. Les faces à prendre
en compte (schéma ci-dessous) sont la face avant (S2,S1,S5,S6), la face de droite
(S6,S5,S4,S7), la face de gauche (S3,S0,S1,S2) et la face arrière (S3,S0,S4,S7).
S3 S7
une brique et ses faces S2
S6
S4
S0
S1 S5
On ajoute une méthode TSphere.CollisionDetecterFaceBrique, qui reçoit en paramètre
une brique TBriqueCube, et qui retourne un tableau faces de 4 entiers de type int. Ce
tableau est initialisé par des valeurs égales à 0. Si la face de gauche est touchée, alors
la valeur 1 est affectée à l’indice 0. Si la face avant est touchée, alors la valeur 1 est
affectée à l’indice 1. Si la face de droite est touchée, alors la valeur 1 est affectée à
l’indice 2. Et si la face arrière est touchée, alors la valeur 1 est affectée à l’indice 3. De
ce fait on sait exactement les faces touchées (valeur 1) des faces non touchées.
304 Programmez des jeux vidéo 3D avec C#5 et WPF
//determiner les faces touchées par collision d’une brique
public int[] CollisionDetecterFaceBrique(TBriqueCube obj_brique) {
int[] faces = new int[4];
Rect3D rect3d_obj_brique = obj_brique.P_BoiteEnglobante;
//face gauche
Point3D s0 = new Point3D(rect3d_obj_brique.Location.X,
rect3d_obj_brique.Location.Y, rect3d_obj_brique.Location.Z);
Point3D s1 = new Point3D(rect3d_obj_brique.Location.X,
rect3d_obj_brique.Location.Y, rect3d_obj_brique.Location.Z +
rect3d_obj_brique.SizeZ);
if (v_boite_englobante.Contains(s0) == true || v_boite_englobante.Contains(s1) ==
true) {
faces[0] = 1;
}
else {
faces[0] = 0;
}
//face avant
Point3D s5 = new Point3D(rect3d_obj_brique.Location.X + rect3d_obj_brique.SizeX,
rect3d_obj_brique.Location.Y, rect3d_obj_brique.Location.Z +
rect3d_obj_brique.SizeZ);
if (v_boite_englobante.Contains(s1) == true || v_boite_englobante.Contains(s5) ==
true) {
faces[1] = 1;
}
else {
faces[1] = 0;
}
//face droite
Point3D s4 = new Point3D(rect3d_obj_brique.Location.X + rect3d_obj_brique.SizeX,
rect3d_obj_brique.Location.Y, rect3d_obj_brique.Location.Z);
if (v_boite_englobante.Contains(s5) == true || v_boite_englobante.Contains(s4) ==
true) {
faces[2] = 1;
}
else {
faces[2] = 0;
}
//face arriere
if (v_boite_englobante.Contains(s4) == true || v_boite_englobante.Contains(s0) ==
Copyright 2013 Patrice REY
true) {
faces[3] = 1;
}
else {
faces[3] = 0;
}
return faces;
}
On donne la priorité aux faces avant et arrière par rapport aux faces de droite et
CHAPITRE 7 Construction du jeu du Casse-Briques 305
de gauche. Si la face avant ou la face arrière d’une brique est touchée, on procède
au changement de direction de la balle, puis on quitte l’instruction de recherche de
collision par une instruction break. Si les faces avant et arrière ne sont pas touchées,
on vérifie si les faces de droite et de gauche sont touchées. Si c’est le cas, on procède
au changement de direction et on quitte l’instruction de recherche de collision par
une instruction break. A noter qu’une brique touchée voit sa visibilité qui passe à l’état
Collapsed. Par la variable index_collision, on stocke l’index de la brique touchée dans
la liste. Dans le cas d’une brique touchée, on la retire de la liste par la méthode List.
RemoveAt une fois la boucle for terminée.
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
if (v_etat_jeu == EtatJeu.en_cours) {
x_raquette.AppliquerTranslation(v_deplac / 320d, 0, 0);
v_vitesse_x += v_vitesse_taux_x;
v_vitesse_z += v_vitesse_taux_z;
x_balle.AppliquerTranslation(v_vitesse_x, 0, v_vitesse_z);
...
//collision avec des briques
int index_collision = -1;
for (int xx = 0; xx < v_liste_brique.Count; xx++) {
modele3d.TBriqueCube brique = v_liste_brique[xx];
if (x_balle.CollisionDetecter(brique) == true) {
int[] faces = x_balle.CollisionDetecterFaceBrique(brique);
//face avant seule ou face arriere seule
if (faces[1] == 1 || faces[3] == 1) {
v_vitesse_taux_z = -v_vitesse_taux_z;
brique.Visibility = Visibility.Collapsed;
index_collision = xx;
break;
}
//face gauche seule ou face droite seule
if (faces[0] == 1 || faces[2] == 1) {
v_vitesse_taux_x = -v_vitesse_taux_x;
brique.Visibility = Visibility.Collapsed;
index_collision = xx;
break;
}
}
}
if (index_collision != -1) {
v_liste_brique.RemoveAt(index_collision);
}
}
...
}
306 Programmez des jeux vidéo 3D avec C#5 et WPF
Le changement de direction de la balle se fait de façon simple (comme celle utilisée
pour la collision de la balle avec les murs):
• si la face avant ou arrière est touchée, on inverse v_vitesse_taux_z,
• si la face de droite ou de gauche est touchée, on inverse v_vitesse_taux_x.
S3 S7 S3 S7
S2 S2
S6 S6
S4 S4
S0 S0
S1 S5 S5
S1
face de droite ou
face de gauche
face avant ou
face arrière
FIGURE 7.21
FIGURE 7.22
308 Programmez des jeux vidéo 3D avec C#5 et WPF
La propriété Visual du VisualBrush reçoit un contenu sous forme d’un ensemble de
contrôles. Pour afficher le score, on affecte à la propriété Visual un TextBlock x_affiche_
score_texte dont sa propriété Foreground est à White et sa propriété FontFamily est à
Quartz MS (police d’écriture avec une apparence dite d’écriture digitale).
<!-- affiche murale pour inscrire le score (à gauche) -->
<modele3d:TAfficheMurale x:Name=ˈx_affiche_scoreˈ P_Largeur=ˈ1.5ˈ P_Hauteur=ˈ0.5ˈ>
<modele3d:TAfficheMurale.P_PointReference>
<Point3D X=ˈ-1.7ˈ Y=ˈ1.35ˈ Z=ˈ-3ˈ></Point3D>
</modele3d:TAfficheMurale.P_PointReference>
<modele3d:TAfficheMurale.P_Texture>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<VisualBrush>
<VisualBrush.Visual>
<TextBlock x:Name=ˈx_affiche_score_texteˈ Text=ˈ00000ˈ Foreground=ˈWhiteˈ
FontFamily=ˈQuartz MSˈ Height=ˈ14ˈ Padding=ˈ0ˈ Canvas.Left=ˈ0ˈ
Canvas.Top=ˈ0ˈ/>
</VisualBrush.Visual>
</VisualBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</modele3d:TAfficheMurale.P_Texture>
</modele3d:TAfficheMurale>
On procède de même pour afficher le temps avec un TextBlock x_affiche_temps_texte
affecté à la propriété Visual d’un VisualBrush.
<!-- affiche murale pour inscrire le temps (à droite) -->
<modele3d:TAfficheMurale x:Name=ˈx_affiche_tempsˈ P_Largeur=ˈ1.5ˈ P_Hauteur=ˈ0.5ˈ>
<modele3d:TAfficheMurale.P_PointReference>
<Point3D X=ˈ0.2ˈ Y=ˈ1.35ˈ Z=ˈ-3ˈ></Point3D>
</modele3d:TAfficheMurale.P_PointReference>
<modele3d:TAfficheMurale.P_Texture>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<VisualBrush>
<VisualBrush.Visual>
<TextBlock x:Name=ˈx_affiche_temps_texteˈ Text=ˈ00:00ˈ Foreground=ˈWhiteˈ
FontFamily=ˈQuartz MSˈ Height=ˈ14ˈ Padding=ˈ0ˈ Canvas.Left=ˈ0ˈ
Copyright 2013 Patrice REY
Canvas.Top=ˈ0ˈ/>
</VisualBrush.Visual>
</VisualBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</modele3d:TAfficheMurale.P_Texture>
</modele3d:TAfficheMurale>
L’écriture du temps écoulé se fait alors très simplement: il suffit de remplacer le
contenu de la propriété Text de x_affiche_temps_texte par une chaîne représentant
CHAPITRE 7 Construction du jeu du Casse-Briques 309
5 - La fin de jeu
Le jeu peut se terminer de plusieurs façons. Si toutes les briques sont détruites alors le
jeu prend fin. Si la raquette échappe la balle et que cette dernière sort du terrain par
le bas, alors le jeu prend fin.
La vérification à effectuer, pour savoir si toutes les briques sont détruites, consiste à
vérifier si la liste v_liste_brique est vide ou pas (par sa propriété Count). Si la liste est
effectivement vide, alors on passe la variable v_etat_jeu à EtatJeu.terminer et on stoppe
l’horloge v_stopwatch.
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
if (v_etat_jeu == EtatJeu.en_cours) {
Copyright 2013 Patrice REY
...
if (v_liste_brique.Count == 0) {
v_etat_jeu = EtatJeu.terminer;
v_stopwatch.Stop();
}
...
}
...
}
CHAPITRE 7 Construction du jeu du Casse-Briques 311
Si la balle est sortie du jeu par le bas cela veut dire que la coordonnée selon Z de sa
boite englobante, à un moment donné, est supérieure à une valeur définie comme
étant hors zone (ici une valeur de 3.7 unités du côté de l’axe Z positif). On passe la
variable v_etat_jeu à EtatJeu.terminer et on stoppe l’horloge v_stopwatch.
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
if (v_etat_jeu == EtatJeu.en_cours) {
...
if (v_liste_brique.Count == 0) {
v_etat_jeu = EtatJeu.terminer;
v_stopwatch.Stop();
}
if (x_balle.P_BoiteEnglobante.Z >= 3.7d) {
v_etat_jeu = EtatJeu.terminer;
v_stopwatch.Stop();
}
...
}
...
}
Dès que v_etat_jeu se trouve à EtatJeu.terminer, on traite cet état de fin de jeu. La fin de
jeu consiste à stopper le rendu d’animation en retirant le gestionnaire de l’événement
Rendering de CompositionTarget. On rend visible le Canvas x_cnv_partie_fin qui
correspond à l’écran d’affichage quand le jeu est terminé. Il se compose d’un TextBlock
x_partie_fin_texte qui affiche un message: soit la partie est perdue, soit la partie est
gagnée en indiquant le nombre de points et le temps écoulé. Un bouton x_btn_rejouer
permet, par un clic, de remettre le jeu à zéro et de recommencer une nouvelle partie.
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
if (v_etat_jeu == EtatJeu.terminer) {
CompositionTarget.Rendering -= CompositionTarget_Rendering;
x_cnv_partie_fin.Visibility = Visibility.Visible;
string message = «»;
if (v_liste_brique.Count != 0) {
message = «Partie perdue !!!»;
}
else {
message = «Partie gagnée avec « + v_score.ToString() + « pts en «;
message += v_stopwatch.Elapsed.Minutes.ToString(«00») + «:» +
v_stopwatch.Elapsed.Seconds.ToString(«00»);
}
x_partie_fin_texte.Text = message;
} ... }
312 Programmez des jeux vidéo 3D avec C#5 et WPF
La figure 7.23 visualise le résultat obtenu quand la partie est terminée: sur le repère 1
la partie est gagnée, et sur le repère 2 la partie est perdue.
FIGURE 7.23
de départ, on réinitialise l’horloge par la méthode Restart, on remet à zéro les variables
v_score et v_nb_image, et on redémarre le rendu d’animation.
//clic sur bouton rejouer une partie
private void x_btn_rejouer_Click(object sender, RoutedEventArgs e) {
x_cnv_partie_fin.Visibility = Visibility.Hidden;
RemplirListeAvecLesBriques();
v_etat_jeu = EtatJeu.en_cours;
x_balle.ReinitialiserEmplacement();
v_vitesse_taux_x = 0d;
v_vitesse_taux_z = -v_vitesse_taux;
v_vitesse_x = v_vitesse_taux_x;
v_vitesse_z = v_vitesse_taux_z;
x_balle.AppliquerTranslation(v_vitesse_x, 0, v_vitesse_z);
v_stopwatch.Restart();
v_stopwatch.Start();
v_score = 0;
v_nb_image = 0;
x_affiche_score_texte.Text = v_score.ToString(«00000»);
x_affiche_temps_texte.Text = v_score.ToString(«00:00»);
x_partie_fin_texte.Text = «???»;
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
8
Bonification du jeu par
l’animation
Au sommaire de ce chapitre
• Apprendre le concept des animations pour le passage entre différents
écrans du jeu
• Programmer un storyboard avec XAML et avec C#
• Programmer l’animation d’un composant par l’intermédiaire d’un
storyboard
• Utiliser et programmer les animations réalistes de façon à ajouter une
touche professionnelle au jeu
• Simuler le roulement d’une balle sur le plateau du jeu
• Apprendre à générer des surfaces de plateau qui ne sont pas planes
• Apprendre à importer des objets 3D au format Wavefront OBJ (objet
exporté par exemple depuis MAYA au format OBJ)
316 Programmez des jeux vidéo 3D avec C#5 et WPF
L’animation est essentielle dans un jeu. Elle permet de rendre le jeu beaucoup plus
attractif par l’ajout de composants animés et elle donne un aspect professionnel aux
transitions dans le passage entre les différents écrans.
Nous allons voir dans ce chapitre comment fonctionne l’animation au travers de ses
classes, et comment nous pouvons utiliser ces classes pour améliorer sensiblement le
jeu dans son fonctionnement.
Généralement, une animation est considérée comme une succession d’images qui
sont visualisées les unes après les autres. L’animation dans WPF utilise un modèle
radicalement différent.
Le modèle d’animation de WPF permet d’animer certaines propriétés des éléments.
Leur progression se configure sur une durée donnée au moyen de différentes stratégies
qui sont:
• une progression linéaire entre une valeur de départ et une valeur cible.
• une application d’algorithmes d’interpolation prédéfinis entre une valeur de départ
et une valeur cible afin d’augmenter le réalisme.
• une progression parmi une série de valeurs intermédiaires au moyen de différents
algorithmes d’interpolation.
Le système d’animation s’applique aux propriétés de dépendances des objets héritant
de DependencyProperty. Une très grande partie des propriétés de la plupart des
objets, y compris non visuels, sont concernées.
Une animation est définie au moyen d’un objet qui dépend du type de la propriété
de dépendance ciblée. Différents types supportent l’animation: Boolean, Byte, Char,
Decimal, Double, Int, Single, Point, Color, Size, Rect, Thickness, String, Object, Vector,
Vector3D, Point3D, Matrix, Rotation3D et Quaternion. Les classes abstraites de
base d’animation correspondantes ont un préfixe qui correspond au type supporté:
DoubleAnimationBase est la classe abstraite de base pour animer des valeurs de type
double, StringAnimationBase est la classe abstraite de base pour animer des valeurs
Copyright 2013 Patrice REY
de type string, etc. La figure 8.1 visualise l’arbre d’héritage. La classe d’animation
dérivée et utilisée pour animer le type correspondant est préfixée par le type: par
exemple DoubleAnimation pour animer le type double, etc.
Toutes les classes abstraites de base d’animation héritent de la classe
AnimationTimeline. Ces classes abstraites de base sont: BooleanAnimationBase qui
anime des valeurs de type bool, ByteAnimationBase qui anime des valeurs de type
byte, CharAnimationBase qui anime des valeurs de type char, DecimalAnimationBase
CHAPITRE 8 Bonification du jeu par l’animation 317
qui anime des valeurs de type decimal, DoubleAnimationBase qui anime des valeurs
de type double, Int16AnimationBase, Int32AnimationBase et Int64AnimationBase qui
animent des valeurs de type int, SingleAnimationBase qui anime des valeurs de type
single, PointAnimationBase qui anime des valeurs de type Point,ColorAnimationBase
qui anime des valeurs de type Color, SizeAnimationBase qui anime des valeurs de type
Size, RectAnimationBase qui anime des valeurs de type Rect, ThicknessAnimationBase
qui anime des valeurs de type Thickness, StringAnimationBase qui anime des
valeurs de type string, ObjectAnimationBase qui anime des valeurs de type object,
VectorAnimationBase qui anime des valeurs de type Vector, Vector3DAnimationBase
qui anime des valeurs de type Vector3D, Point3DAnimationBase qui anime des
valeurs de type Point3D, MatrixAnimationBase qui anime des valeurs de type
Matrix, Rotation3DAnimationBase qui anime des valeurs de type Rotation3D, et
QuaternionAnimationBase qui anime des valeurs de type Quaternion.
FIGURE 8.1
318 Programmez des jeux vidéo 3D avec C#5 et WPF
Les animations basiques font varier sur une durée donnée (propriété Duration) la
valeur d’une propriété cible entre une valeur de départ (propriété From) et une valeur
finale (propriété To), ou à partir de la valeur courante en fonction d’un incrément
(propriété By). Ces animations sont qualifiées d’animation From/To/By. Ces classes
sont ByteAnimation, ColorAnimation, DecimalAnimation, DoubleAnimation,
Int16Animation, Int32Animation, Int64Animation, Point3DAnimation,
PointAnimation, QuaternionAnimation, RectAnimation, Rotation3DAnimation,
SingleAnimation, SizeAnimation, ThicknessAnimation, Vector3DAnimation et
VectorAnimation (figure 8.2).
Les animation d’images clé (animations dites KeyFrames) permettent de
définir des valeurs intermédiaires et leurs paramètres d’interpolation. Les
noms de classe sont préfixés par le type et sont suffixés de UsingKeyFrames
comme les classes (figure 8.2): BooleanAnimationUsingKeyFrames qui anime
des valeurs de type bool, ByteAnimationUsingKeyFrames qui anime des
valeurs de type byte, CharAnimationUsingKeyFrames qui anime des valeurs
de type char, DecimalAnimationUsingKeyFrames qui anime des valeurs
de type decimal, DoubleAnimationUsingKeyFrames qui anime des valeurs
de type double, IntAnimationUsingKeyFrames (Int16.., Int32.., Int64..) qui
anime des valeurs de type int, SingleAnimationUsingKeyFrames qui anime
des valeurs de type single, PointAnimationUsingKeyFrames qui anime des
valeurs de type Point, ColorAnimationUsingKeyFrames qui anime des valeurs
de type Color, SizeAnimationUsingKeyFrames qui anime des valeurs de
type Size, RectAnimationUsingKeyFrames qui anime des valeurs de type
Rect, ThicknessAnimationUsingKeyFrames qui anime des valeurs de type
Thickness, StringAnimationUsingKeyFrames qui anime des valeurs de type
string, ObjectAnimationUsingKeyFrames qui anime des valeurs de type
object, VectorAnimationUsingKeyFrames qui anime des valeurs de type
Vector, Vector3DAnimationUsingKeyFrames qui anime des valeurs de type
Vector3D, Point3DAnimationUsingKeyFrames qui anime des valeurs de type
Point3D, MatrixAnimationUsingKeyFrames qui anime des valeurs de type Matrix,
Rotation3DAnimationUsingKeyFrames qui anime des valeurs de type Rotation3D, et
Copyright 2013 Patrice REY
Toutes les animations ainsi que la classe Storyboard, héritent de la classe Timeline.
On peut traduire le terme Timeline par chronologie. Un objet Timeline représente
un segment de temps correspondant à l’exécution d’une animation. Il dispose de
propriétés permettant de définir sa durée, son moment relatif au démarrage, s’il doit
être répété, sa réversibilité, son facteur d’accélération, sa vitesse et son comportement
en fin de séquence. De plus il supporte l’imbrication.
Le Storyboard est un conteneur d’objets Timeline par sa propriété Children. Il est donc
possible d’imbriquer plusieurs Storyboard. La durée de base d’un objet Timeline est
déterminée par la propriété Duration, qui peut être définie en XAML par une chaîne
au format:
[ jour.]heures:minutes[:secondes[.fractions]]
Duration = «0:0:0.8»
Duration = «00:5:20»
Un objet Duration peut être initialisé par le code au moyen d’un objet TimeSpan
passé au constructeur et obtenu à partir d’une méthode statique telle que TimeSpan.
FromSeconds.
Duration duree= new Duration(TimeSpan.FromSeconds(0.8));
La propriété BeginTime, de type TimeSpan, indique le temps à partir duquel l’exécution
commence. Ce temps est relatif au Storyboard conteneur, ou à son déclenchement s’il
s’agit de l’objet racine.
<Storyboard BeginTime= «0:0:8» />
La propriété RepeatBehavior permet de configurer la répétition d’un objet Timeline
ainsi que la façon de le répéter. Elle définit:
• soit le nombre d’exécutions de l’objet Timeline (propriété Count).
• soit la durée de l’exécution (propriété Duration); l’exécution est répétée tant que
cette durée n’est pas atteinte.
La propriété statique RepeatBehavior.Forever renvoie un objet RepeatBehavior qui
représente une durée de répétition infinie. Cet objet peut être assigné à la propriété
RepeatBehavior de l’objet Timeline pour répéter en boucle son exécution.
<Storyboard RepeatBehavior= «Forever» />
La propriété booléenne AutoReverse permet d’inverser l’exécution d’un objet Timeline
après une première exécution normale.
<Storyboard AutoReverse= «True» />
La propriété SpeedRatio permet de faire varier la vitesse d’exécution d’un objet Timeline
par un facteur correspondant à sa valeur (1.0 par défaut) et relatif au conteneur. Les
322 Programmez des jeux vidéo 3D avec C#5 et WPF
facteurs des différents niveaux se multiplient entre eux: si un conteneur a un SpeedRatio
de 3.0 et que le storyboard imbriqué a un facteur de 2.0, le facteur résultant sera de 6.
La propriété énumérée FillBehavior détermine ce qu’il advient de la valeur de la
propriété cible à l’issue d’une animation:
• la valeur HoldEnd (par défaut) indique que la valeur finale de l’animation est
conservée.
• la valeur Stop indique que la valeur initiale de la propriété cible avant l’animation
est restaurée.
<Storyboard FillBehavior= «Stop» />
L’animation est couramment employée pour réaliser des transitions dans le passage
d’un écran à l’autre. Comme au début du jeu, un bouton permet de démarrer le jeu.
Une animation contrôlée permet d’effectuer une transition dans laquelle un temps
court s’écoule, et durant lequel des actions se déroulent en arrière-plan (comme des
initialisations, une mise en place d’objet, le démarrage d’une horloge, etc.).
Jusqu’à présent dans notre jeu, le lancement du jeu se faisait en cliquant sur le
bouton Lancer le jeu, l’écran perdait sa visibilité et le jeu démarrait automatiquement.
Maintenant nous allons ajouter des animations qui vont servir de transition entre le
passage des écrans.
La première animation ajoutée (figure 8.4) consiste à réduire l’écran de départ d’une
façon proportionnelle en largeur et en hauteur (repères 1 et 2 de la figure 8.4), durant
un temps déterminé, dévoilant ainsi en arrière-plan la surface du jeu.
La deuxième animation ajoutée (figure 8.5) consiste à réaliser un composant qui,
durant une chronologie de 4 secondes, affiche un décompte horaire allant de 3 à 0
avec un arc de cercle doté d’une épaisseur qui se déroule de 0° à 360°. Sur le repère 1
de la figure 8.5, le décompte affiche 3 et l’arc se déroule avec un angle de 0° à 90°. Sur
Copyright 2013 Patrice REY
2
FIGURE 8.5
4
CHAPITRE 8 Bonification du jeu par l’animation 325
La troisième et dernière animation ajoutée (figure 8.6) consiste à faire apparaître l’écran
de fin de jeu par modification de son opacité. Le jeu se terminant (partie gagnée ou
partie perdue), l’écran de fin de jeu devient visible avec une opacité fixée à 0 (signifiant
que l’écran est complètement transparent). Puis l’animation consiste à faire passer, de
façon continue, l’opacité de l’écran de fin de jeu de la valeur 0 à la valeur 1 (signifiant
l’écran complètement opaque).
FIGURE 8.6
Le fait d’ajouter une animation entre l’écran de départ (jeu à l’arrêt) et l’écran de jeu
en cours nous oblige à rajouter une étape dans le cours du jeu qui est un prélude.
L’énumération EtatJeu est complétée par EtatJeu.prelude (elle possèdait déjà EtatJeu.
arret, EtatJeu.en_cours, et EtatJeu.terminer).
Jusqu’à présent, l’écran de départ, doté d’un bouton pour lancer le jeu, était représenté
par un Canvas x_cnv_lancer. Comme nous allons modifier la taille horizontale et verticale
de l’écran par une chronologie, on remplace le Canvas par un Grid. De cette façon,
quand le contrôle Grid verra ses dimensions réduites, tous les objets qu’il contient
resteront centrés sur le Grid. Avec un Canvas, comme les objets sont positionnés par
326 Programmez des jeux vidéo 3D avec C#5 et WPF
les propriétés attachées Canvas.Top et Canvas.Left, il aurait fallu procéder aussi à une
modification supplémentaire liée au positionnement sur le Canvas. C’est pourquoi il
est préférable d’opter pour un contrôle de type Grid. Le code XAML du Grid x_grid_
lancer est donc le suivant:
<!-- grid initial de lancement de jeu -->
<Grid x:Name=ˈx_grid_lancerˈ Background=ˈ#93FF9494ˈ Width=ˈ1274ˈ Height=ˈ732ˈ
HorizontalAlignment=ˈCenterˈ VerticalAlignment=ˈCenterˈ Visibility=ˈHiddenˈ>
<Button x:Name=ˈx_btn_lancerˈ Content=ˈLancer le jeuˈ Width=ˈ190ˈ Height=ˈ67ˈ
Style=ˈ{StaticResource style_bouton}ˈ FontSize=ˈ20ˈ Click=ˈx_btn_lancer_Clickˈ
HorizontalAlignment=ˈCenterˈ VerticalAlignment=ˈCenterˈ/>
</Grid>
Ce Grid x_grid_lancer possède un fond semi-transparent (propriété Background), il
est centré verticalement et horizontalement dans son conteneur hôte (propriétés
HorizontalAlignment et VerticalAlignment fixées à Center), sa visibilité est cachée
(propriété Visibility fixée à Hidden), et ses dimensions sont fixées explicitement
(propriétés Width = 1274 et Height = 732). Il est important de fixer explicitement les
propriétés Width et Height, de type double, si l’on veut pouvoir les modifier par une
animation, car par défaut elles sont définies à la valeur Auto. Pour le type double, la
valeur Auto est égale à la valeur Double.NaN. La valeur Double.NaN est une valeur
qui n’est pas un nombre mais qui représente le mode automatique par défaut. D’où
l’obligation de définir explicitement la valeur de type double pour pouvoir procéder à
sa modification par la suite.
L’ajout d’une animation par une chronologie se fait en ajoutant un objet Storyboard.
Comme l’objet Storyboard n’est pas un objet héritant de FrameworkElement,
directement ou indirectement, on ne peut donc pas l’ajouter en XAML comme un
contrôle ordinaire (Grid, Canvas, etc.). La solution consiste à le placer comme une
ressource dotée d’une clé de référence, et pour l’utiliser, il suffira de le rechercher par
un appel dans les ressources avec sa clé de référence.
Nous allons ajouter le storyboard aux ressources du Grid en lui notifiant une clé de
référence. Pour ajouter des ressources au Grid x_grid_lancer, on ajoute une balise <Grid.
Resources> qui va contenir toutes les ressources nécessaires. La propriété Resources
d’un contrôle est un dictionnaire contenant une collection de type ResourceDictionary.
Copyright 2013 Patrice REY
Pour se familiariser avec la programmation des animations, nous allons d’abord créer
une animation qui effectue une réduction de la dimension horizontale du Grid lors
d’une chronologie dont la durée est de 1 seconde. Le schéma ci-dessous visualise ce
que nous allons effectuer. Au temps 0 seconde, l’écran recouvre toute la surface, et au
temps 1 seconde, la dimension horizontale de l’écran est nulle.
temps = 0 seconde
temps = 1 seconde
328 Programmez des jeux vidéo 3D avec C#5 et WPF
Dans les ressources du Grid, on ajoute un objet Storyboard doté d’une clé de référence
k_stb_reduction_hori. L’attribution d’une clé de référence se fait par x:Key en XAML. La
propriété à modifier est Width du contrôle Grid. Elle est de type double. Donc, comme
on a vu au début de ce chapitre, pour modifier une valeur de type double, on utilise
une animation de type DoubleAnimation. Dans le storyboard, on ajoute une piste de
type DoubleAnimation (les pistes sont ajoutées implicitement à la propriété Children
de Storyboard).
<!-- grid initial de lancement de jeu -->
<Grid x:Name=ˈx_grid_lancerˈ Background=ˈ#93FF9494ˈ Width=ˈ1274ˈ Height=ˈ732ˈ
HorizontalAlignment=ˈCenterˈ VerticalAlignment=ˈCenterˈ Visibility=ˈHiddenˈ>
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_reduction_horiˈ >
<DoubleAnimation> ... </DoubleAnimation>
</Storyboard>
</Grid.Resources>
<Button x:Name=ˈx_btn_lancerˈ Content=ˈLancer le jeuˈ Width=ˈ190ˈ Height=ˈ67ˈ
Style=ˈ{StaticResource style_bouton}ˈ FontSize=ˈ20ˈ Click=ˈx_btn_lancer_Clickˈ
HorizontalAlignment=ˈCenterˈ VerticalAlignment=ˈCenterˈ/>
</Grid>
L’animation DoubleAnimation cible la propriété Width de x_grid_lancer. Donc on ajoute
la propriété attachée Storyboard.TargetName en lui affectant le nom du contrôle à
référencer (Storyboard.TargetName=ˈx_grid_lancerˈ), et on ajoute la propriété attachée
Storyboard.TargetProperty en lui affectant le nom de la propriété ciblée (Storyboard.
TargetProperty=ˈWidthˈ). La chaîne qui est affectée à la propriété TargetProperty est
du type PropertyPath. Un objet PropertyPath implémente une structure de donnée
représentant un chemin pour accéder à une propriété de dépendance choisie. Ici on
exprime ce chemin par la chaîne Width (propriété Width de Grid). On aurait pu écrire
aussi la chaîne (Grid.Width) pour exprimer la même chose (attention, les parenthèses
ouvrante et fermante sont obligatoires).
<!-- grid initial de lancement de jeu -->
<Grid x:Name=ˈx_grid_lancerˈ Background=ˈ#93FF9494ˈ Width=ˈ1274ˈ Height=ˈ732ˈ
HorizontalAlignment=ˈCenterˈ VerticalAlignment=ˈCenterˈ Visibility=ˈHiddenˈ>
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_reduction_horiˈ >
Copyright 2013 Patrice REY
<DoubleAnimation Storyboard.TargetName=ˈx_grid_lancerˈ
Storyboard.TargetProperty=ˈWidthˈ>
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
<Button x:Name=ˈx_btn_lancerˈ Content=ˈLancer le jeuˈ Width=ˈ190ˈ Height=ˈ67ˈ
Style=ˈ{StaticResource style_bouton}ˈ FontSize=ˈ20ˈ Click=ˈx_btn_lancer_Clickˈ
HorizontalAlignment=ˈCenterˈ VerticalAlignment=ˈCenterˈ/>
</Grid>
CHAPITRE 8 Bonification du jeu par l’animation 329
Comme on est dans le cas simple d’une animation du type from/to/by, on exprime avec
la propriété From la valeur de départ de la cible (From = «1274»), avec la propriété To
la valeur de fin de la cible (To = «0»), et avec la propriété By la valeur de l’incrément
souhaité (By = «1» pour un incrément de 1 pixel). La propriété Duration exprime la
durée de la chronologie, donc pour une chronologie de 1 seconde on écrira Duration
= «00:00:01» (format heure:minute:seconde). On fixe la propriété AutoReverse à false de
façon à ce qu’elle ne revienne pas au départ par le chemin inverse.
<!-- grid initial de lancement de jeu -->
<Grid x:Name=ˈx_grid_lancerˈ Background=ˈ#93FF9494ˈ Width=ˈ1274ˈ Height=ˈ732ˈ
HorizontalAlignment=ˈCenterˈ VerticalAlignment=ˈCenterˈ Visibility=ˈHiddenˈ>
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_reduction_horiˈ >
<DoubleAnimation Storyboard.TargetName=ˈx_grid_lancerˈ
Storyboard.TargetProperty=ˈWidthˈ From=ˈ1274ˈ To=ˈ0.00ˈ By=ˈ1.00ˈ
Duration=ˈ00:00:01ˈ AutoReverse=ˈFalseˈ>
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
<Button x:Name=ˈx_btn_lancerˈ Content=ˈLancer le jeuˈ Width=ˈ190ˈ Height=ˈ67ˈ
Style=ˈ{StaticResource style_bouton}ˈ FontSize=ˈ20ˈ Click=ˈx_btn_lancer_Clickˈ
HorizontalAlignment=ˈCenterˈ VerticalAlignment=ˈCenterˈ/>
</Grid>
A ce stade l’animation est programmée dans ses fonctionnalités. Maintenant nous
allons voir comment l’exécuter.
Cette animation doit être lancée quand on clique sur le bouton x_btn_lancer. Dans
le gestionnaire de l’événement Click de ce bouton, on ajoute une variable story de
type Storyboard et on lui affecte le storyboard dont la clé de référence est k_stb_
reduction_hori. La propriété Resources du contrôle Grid x_grid_lancer est du type
ResourceDictionary (c’est-à-dire une collection de ressources sous forme d’une liste
générique). Pour accéder à une ressource identifiée par une clé de référence x:Key,
on appelle la propriété Resources en lui passant la chaîne représentant la clé de
référence (x_grid_lancer.Resources[«k_stb_reduction_hori»]), et on effectue un cast pour
le résultat obtenu (ici un cast en Storyboard). Comme on a stocké le storyboard dans
le dictionnaire des ressources de x_grid_lancer, on recherche donc la ressource dans ce
dictionnaire par x_grid_lancer.Resources[..].
Pour démarrer cette chronologie, on applique la méthode Storyboard.Begin.
L’événement Completed de Storyboard se déclenche quand la chronologie est terminée.
On ajoute alors un gestionnaire pour l’événement Completed. Dans ce gestionnaire
intitulé story_Completed, on inscrira les tâches à effectuer pour la poursuite du jeu.
Dès lors, le déclenchement de la chronologie s’effectue bien lors du clic sur le bouton.
330 Programmez des jeux vidéo 3D avec C#5 et WPF
//bouton de lancement du jeu
private void x_btn_lancer_Click(object sender, RoutedEventArgs e) {
x_btn_lancer.IsEnabled = false;
Storyboard story = (Storyboard)x_grid_lancer.Resources[«k_stb_reduction_hori»];
story.Completed += story_Completed;
story.Begin();
}
//evenement storyboard termine
private void story_Completed(object sender, EventArgs e) {
//MessageBox.Show(«storyboard terminé»);
x_grid_lancer.Visibility = Visibility.Hidden;
}
Supposons que l’on souhaite effectuer ce même type d’animation uniquement sur la
propriété Height du Grid (la dimension verticale du Grid), la visualisation de cet effet
serait donc la suivante:
temps = 1
seconde
temps = 0 seconde
temps = 1 seconde
Copyright 2013 Patrice REY
3
2
3
t=1
2
t=0
DEBUT
0 FIN
t=4
t=2
1
0 t=3
Copyright 2013 Patrice REY
0 1
CHAPITRE 8 Bonification du jeu par l’animation 335
L’arc du compte à rebours est un composant de type Arc, dérivant de la classe abstraite
Shape. La classe Shape est une classe de base qui sert à dessiner des éléments d’une
figure comme le rectangle, l’ellipse, le polygone, etc.
On ajoute une classe Arc qui hérite de Shape (fichier Arc.cs). Pour animer la rotation de
l’arc, il faudra pouvoir modifier à un instant donné la valeur de l’angle de fin en fonction
de l’angle de début, de façon à dessiner l’arc entre ces deux angles. On dote alors l’arc
de deux propriétés de dépendance qui sont StartAngle et EndAngle, que l’on initialise
à une valeur nulle de type double. Pour permettre à une modification automatique de
l’arc en cas de modification de la valeur des propriétés de dépendances StartAngle et
EndAngle, on utilise l’énumération FrameworkPropertyMetadataOptions.AffectsRender
dans la déclaration de la propriété de dépendance.
public class Arc : Shape {
//propriete de dependance StartAngle
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register(
«StartAngle», typeof(double), typeof(Arc),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.AffectsRender));
public double StartAngle {
get { return (double)GetValue(StartAngleProperty); }
set { SetValue(StartAngleProperty, value); }
}
//propriete de dependance EndAngle
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register(
«EndAngle», typeof(double), typeof(Arc),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.AffectsRender));
public double EndAngle {
get { return (double)GetValue(EndAngleProperty); }
set { SetValue(EndAngleProperty, value); }
}
...
}
La propriété héritée DefiningGeometry retourne la géométrie de la figure. C’est cette
propriété qui dessine l’arc. Il faut donc redéfinir cette propriété pour y implémenter
le dessin de notre arc. Le dessin de l’arc consiste à trouver la position du point pour
l’angle StartAngle, la position du point pour l’angle EndAngle, et alors dessiner un arc
entre ces deux points. A noter que les valeurs, de type double, que l’on assigne aux
propriétés StartAngle et EndAngle représentent des valeurs d’angles en degrés. Avec
ce niveau d’abstraction, on peut alors aisément modifier les valeurs des angles des
propriétés de dépendances même avec un incrément non entier.
336 Programmez des jeux vidéo 3D avec C#5 et WPF
public class Arc : Shape {
...
//redefinition de la propriete qui represente la geometrie de la figure
protected override Geometry DefiningGeometry {
get { return ConstructionArc(); }
}
//definir la geometrie de la forme
private Geometry ConstructionArc() {
Point startPoint = PointAtAngle(Math.Min(StartAngle, EndAngle));
Point endPoint = PointAtAngle(Math.Max(StartAngle, EndAngle));
Size arcSize = new Size(Math.Max(0, (RenderSize.Width - StrokeThickness) / 2),
Math.Max(0, (RenderSize.Height - StrokeThickness) / 2));
bool isLargeArc = Math.Abs(EndAngle - StartAngle) > 180;
StreamGeometry geom = new StreamGeometry();
using (StreamGeometryContext context = geom.Open()) {
context.BeginFigure(startPoint, false, false);
context.ArcTo(endPoint, arcSize, 0, isLargeArc,
SweepDirection.Counterclockwise, true, false);
}
geom.Transform = new TranslateTransform(StrokeThickness / 2,
StrokeThickness / 2);
return geom;
}
//
private Point PointAtAngle(double angle) {
double radAngle = angle * (Math.PI / 180);
double xRadius = (RenderSize.Width - StrokeThickness) / 2;
double yRadius = (RenderSize.Height - StrokeThickness) / 2;
double x = xRadius + xRadius * Math.Cos(radAngle);
double y = yRadius - yRadius * Math.Sin(radAngle);
return new Point(x, y);
}
...
}
D’où la définition XAML du Grid x_grid_cpt_rebours est la suivante, avec un arc dont
les propriétés StartAngle et EndAngle sont initialisées à 0°. On personnalise l’arc en lui
donnant une épaisseur (propriété StrokeThickness) et une couleur de pinceau pour le
remplissage (propriété Stroke avec un dégradé de couleurs ici).
Copyright 2013 Patrice REY
<Grid.Resources>
<Storyboard x:Key=ˈstb_arc_reboursˈ>
<DoubleAnimation Storyboard.TargetName=ˈx_arc_reboursˈ
Storyboard.TargetProperty=ˈEndAngleˈ From=ˈ0.00ˈ To=ˈ359.99ˈ
Duration=ˈ00:00:04ˈ AutoReverse=ˈFalseˈ BeginTime=ˈ00:00:00ˈ>
</DoubleAnimation>
<StringAnimationUsingKeyFrames
Storyboard.TargetName=ˈx_arc_rebours_textˈ
Storyboard.TargetProperty=ˈ(TextBlock.Text)ˈ Duration=ˈ00:00:04ˈ
BeginTime=ˈ00:00:00ˈ>
<DiscreteStringKeyFrame Value=ˈ3ˈ KeyTime=ˈ0:0:0ˈ />
<DiscreteStringKeyFrame Value=ˈ2ˈ KeyTime=ˈ0:0:1ˈ />
<DiscreteStringKeyFrame Value=ˈ1ˈ KeyTime=ˈ0:0:2ˈ />
<DiscreteStringKeyFrame Value=ˈ0ˈ KeyTime=ˈ0:0:3ˈ />
</StringAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
...
</Grid>
Pour déclencher cette animation du compte à rebours, on place dans le gestionnaire de
l’événement Completed de la précédente animation un déclencheur qui va rechercher
le storyboard de référence stb_arc_rebours dans le dictionnaire des ressources de x_
grid_cpt_rebours, et qui lui applique la méthode Storyboard.Begin.
340 Programmez des jeux vidéo 3D avec C#5 et WPF
On en profite pour activer le rendu image par image (avec CompositionTarget.Rendering).
Cela permet d’activer le rendu et donc de pouvoir déplacer la raquette avec la souris.
Et on passe le jeu dans l’état EtatJeu.prelude.
La question que l’on pourrait se poser concerne l’utilité de l’énumération EtatJeu.prelude.
Le fait de passer par cette énumération permet d’activer ou non des fonctionnalités.
Dans le gestionnaire du rendu image par image (gestionnaire CompositionTarget_
Rendering), une instruction conditionnelle teste la variable v_etat_jeu, et si sa valeur
est EtatJeu.prelude, alors on active la possibilité de déplacement de la raquette par la
méthode AppliquerTranslation appliquée à la raquette.
//evenement storyboard termine
private void story_Completed(object sender, EventArgs e) {
//MessageBox.Show(«storyboard terminé»);
x_grid_lancer.Visibility = Visibility.Hidden;
x_grid_cpt_rebours.Visibility = Visibility.Visible;
CompositionTarget.Rendering += CompositionTarget_Rendering;
v_etat_jeu = EtatJeu.prelude;
Storyboard stb_arc_rebours =
(Storyboard)x_grid_cpt_rebours.Resources[«stb_arc_rebours»];
stb_arc_rebours.Completed += stb_arc_rebours_Completed;
stb_arc_rebours.Begin();
}
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
if (v_etat_jeu == EtatJeu.prelude) {
x_raquette.AppliquerTranslation(v_deplac / 320d, 0, 0);
}
...
}
On ajoute un gestionnaire pour l’événement Completed du compte à rebours de façon
à procéder aux initialisations nécessaires pour le démarrage proprement dit du jeu (le
jeu passe à l’état EtatJeu.en_cours).
//evenement storyboard arc termine
private void stb_arc_rebours_Completed(object sender, EventArgs e) {
x_grid_cpt_rebours.Visibility = Visibility.Hidden;
v_etat_jeu = EtatJeu.en_cours;
Copyright 2013 Patrice REY
v_vitesse_taux_x = 0d;
v_vitesse_taux_z = -v_vitesse_taux;
v_stopwatch.Start();
v_score = 0;
v_apparition_boom_balle = 0;
x_affiche_score_texte.Text = v_score.ToString(«00000»);
x_affiche_temps_texte.Text = v_score.ToString(«00:00»);
x_partie_fin_texte.Text = «???»;
}
CHAPITRE 8 Bonification du jeu par l’animation 341
1 temps = 0 seconde
Dans ce paragraphe, nous allons nous intéresser à la façon de réaliser une apparition
brève d’un effet d’éclair électrique lorsque la balle entre en collision avec la raquette.
Chaque fois qu’une collision se produit entre la balle et la raquette, il faut faire
apparaître un éclair électrique pendant une courte durée pour donner l’impression
que la balle cogne fort sur la raquette.
Un éclair est par définition une lumière intense et brève, en général sinueuse et ramifiée,
provoquée par une décharge disruptive entre deux nuages ou entre un nuage et le sol,
lors d’un orage. Comme le visualise la figure 8.9, un éclair apparaît pendant quelques
millisecondes lors de la collision entre la balle et la raquette. Rien qu’en image, on voit
bien l’impact professionnel qu’un éclair électrique apporte au jeu.
FIGURE 8.9
De façon à apporter un réalisme certain avec cet éclair électrique, on s’arrange pour que
ce dernier se produise à partir de la raquette dans le plan horizontal (OXZ). Le schéma
ci-dessous montre, d’un point de vue conceptuel, la façon de réaliser simplement cet
effet d’éclair électrique.
TBoom x_boom
On modélise une affichette sur laquelle on applique une texture qui représente un
éclair électrique. La texture doit être au format PNG avec la transparence activée. Ici
on utilise le fichier image boom_balle_2.png (stocké dans le dossier contenu/boom/)
dont les parties blanches du fond sont transparentes.
Cette affichette est du type TBoom. On ajoute une classe TBoom qui hérite de
UIElement3D dans l’espace de noms CasseBrique3D.modele3d, et on lui ajoute les
propriétés de dépendances P_Modele, P_PointReference, P_Largeur, P_Profondeur,
P_CouleurUnie et P_Texture. Sa modélisation est une simple surface rectangulaire
parallèle au plan OXZ. La méthode ajoutée AppliquerTranslation réalise, comme à
l’habitude, une translation de l’objet en fonction des paramètres passés. En XAML, on
ajoute une instance de TBoom nommée x_boom, et on la positionne devant la raquette
348 Programmez des jeux vidéo 3D avec C#5 et WPF
et de façon centrée. A son initialisation, ce contrôle est caché.
<!-- le boom sur la raquette -->
<modele3d:TBoom x:Name=ˈx_boomˈ P_Largeur=ˈ2ˈ P_Profondeur=ˈ3ˈ
Visibility=ˈHiddenˈ >
<modele3d:TBoom.P_PointReference>
<Point3D X=ˈ-1ˈ Y=ˈ0ˈ Z=ˈ3.4ˈ></Point3D>
</modele3d:TBoom.P_PointReference>
<modele3d:TBoom.P_Texture>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource=ˈcontenu/boom/boom_balle_2.pngˈ></ImageBrush>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</modele3d:TBoom.P_Texture>
</modele3d:TBoom>
En cas de collision entre la balle et la raquette, l’éclair électrique doit être visible
pendant un temps bref. Le rendu d’animation a une fréquence de l’ordre de 60 images
par seconde. Si on fait apparaître l’éclair électrique pendant la visualisation de 5 images
consécutives, cela correspond à une durée moyenne de 60/5 soit 0.12 seconde (12
millisecondes).
D’un point de vue programmation, on ajoute une variable v_apparition_boom_balle
que l’on initialise à 0. Quand une collision entre la balle et la raquette est détectée
(x_balle.CollisionDetecter(x_raquette) == true), l’éclair x_boom devient visible (propriété
Visibility.Visible), on le positionne en lui appliquant une translation identique à celle
appliquée à la raquette par x_boom.AppliquerTranslation(v_deplac / 320d, 0, 0), et on
incrémente la variable v_apparition_boom_balle.
//rendu image par image
private void CompositionTarget_Rendering(object sender, EventArgs e) {
...
if (v_etat_jeu == EtatJeu.en_cours) {
...
if (x_balle.CollisionDetecter(x_raquette) == true) {
...
//affichage de l’eclair de choc
x_boom.Visibility = Visibility.Visible;
Copyright 2013 Patrice REY
Valeurs
= f(t)
Temps t
la seconde moitié.
La classe SineEase représente une fonction d’accélération qui crée une animation
qui accélère et/ou ralentit à l’aide d’une formule sinus (figure 8.12). Cette fonction
d’accélération est construite d’après la formule sinus suivante:
f(t) = 1 - [ sin(1-t) * π/2 ]
CHAPITRE 8 Bonification du jeu par l’animation 351
FIGURE 8.11
FIGURE 8.12
Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
352 Programmez des jeux vidéo 3D avec C#5 et WPF
La classe BounceEase représente une fonction d’accélération qui crée un effet de
rebondissement animé. Sa propriété Bounces (de type int) fixe le nombre de rebonds et
sa propriété Bounciness (de type double) fixe le dégré d’élasticité des rebonds (facteur
de décroissance des rebonds). La figure 8.13 visualise la représentation de sa courbe.
FIGURE 8.13
Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
La classe BackEase représente une fonction d’accélération qui rétracte le mouvement
d’une animation un peu avant qu’elle ne commence à s’animer (figure 8.14). Sa propriété
Amplitude, de type double, fixe la valeur de l’amplitude de la rétractation (cette valeur
doit être supérieure ou égale à 0, avec une valeur de 1 par défaut). La formule de
représentation de cette fonction est du type (avec A représentant le coefficient de
l’amplitude): f(t) = t3 - t * A * ( sin(t) * π )
Copyright 2013 Patrice REY
CHAPITRE 8 Bonification du jeu par l’animation 353
FIGURE 8.14
Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
La classe CircleEase représente une fonction d’accélération qui crée une animation qui
accélère et/ou ralentit à l’aide d’une fonction circulaire (figure 8.15).
FIGURE 8.15
Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
354 Programmez des jeux vidéo 3D avec C#5 et WPF
La classe ElasticEase représente une fonction d’accélération qui crée une animation qui
ressemble à un ressort oscillant d’avant en arrière jusqu’à ce qu’il se stabilise (figure
8.16). Sa propriété Oscillations, de type int, fixe le nombre d’aller-retour sous forme
d’oscillation, et sa propriété Springiness, de type double, fixe le facteur de décroissance
des oscillations.
FIGURE
8.16
Valeurs Valeurs
= f(t) = f(t) mode EaseOut
mode EaseIn
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
356 Programmez des jeux vidéo 3D avec C#5 et WPF
La classe QuadraticEase (figure 8.18) représente une fonction d’accélération qui crée
une animation qui accélère et/ou ralentit à l’aide de la formule f(t) = t2 .
FIGURE
8.18
Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
La classe CubicEase (figure 8.19) représente une fonction d’accélération qui crée une
animation qui accélère et/ou ralentit à l’aide de la formule f(t) = t3 .
FIGURE
8.19 Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)
Temps t Temps t
Copyright 2013 Patrice REY
Valeurs
= f(t)
mode EaseInOut
Temps t
CHAPITRE 8 Bonification du jeu par l’animation 357
La classe QuarticEase (figure 8.20) représente une fonction d’accélération qui crée une
animation qui accélère et/ou ralentit à l’aide de la formule f(t) = t4 .
FIGURE
8.20 Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
La classe QuinticEase (figure 8.21) représente une fonction d’accélération qui crée une
animation qui accélère et/ou ralentit à l’aide de la formule f(t) = t5 .
FIGURE
8.21
Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
358 Programmez des jeux vidéo 3D avec C#5 et WPF
La classe PowerEase (figure 8.22) représente une fonction d’accélération qui crée une
animation qui accélère et/ou ralentit à l’aide de la formule f(t) = tP (avec P représentant
l’exposant de la puissance). Sa propriété Power fixe l’exposant de la puissance, de type
double, avec une valeur de 2 par défaut et supérieure ou égale à 0.
FIGURE
8.22
Valeurs Valeurs
mode EaseIn mode EaseOut
= f(t) = f(t)
Temps t Temps t
Valeurs
= f(t)
mode EaseInOut
Temps t
Nous allons mettre en application le principe des animations réalistes lors du déplacement
d’une brique sur le plateau du jeu (dossier 04_VS2012_3d_anim_realiste_1). Pour cela,
une simple application 3D à part (figure 8.23) permet de mettre en évidence l’utilisation
des animations réalistes au travers des 11 classes décrites précédemment plus une
animation personnalisée (pour en comprendre l’implémentation et l’utilisation) et plus
une animation de type classique. Un sélecteur sous forme d’un ComboBox (repère 1
Copyright 2013 Patrice REY
de la figure 8.23) permet de choisir une fonction d’accélération parmi les 11 fonctions
prédéfinies plus une fonction personnalisée et une fonction classique, puis le bouton
«Jouer l’animation» permet d’appliquer l’animation réaliste au déplacement d’une
brique (de type TCube) le long de l’axe X et de façon parallèle. Un cylindre (de type
TCylindre) est positionné au milieu de la brique pour visualiser plus facilement l’impact
de l’effet produit par l’animation réaliste (repère 2 de la figure 8.23).
FIGURE 8.23
2
360 Programmez des jeux vidéo 3D avec C#5 et WPF
Sur la scène, on positionne un plateau de jeu x_sol, de type TSol, texturé avec une
image graphique, une brique x_cube, de type TCube, texturée avec une image
graphique, et un cylindre x_cylindre, de type TCylindre, texturé également. On ajoute à
la propriété Transform de la brique x_cube une transformation x_cube_translate de type
TranslateTransform3D, dont les propriétés OffsetX, OffsetY et OffsetZ sont fixées à 0.
La variable v_choix stocke le choix de l’animation choisie au travers d’une énumération
de type ChoixAnim (ChoixAnim.classique, ChoixAnim.sine, ChoixAnim.back, ChoixAnim.
bounce, ChoixAnim.circle, ChoixAnim.cubic, ChoixAnim.elastic, ChoixAnim.exponent,
ChoixAnim.power, ChoixAnim.quadratic, ChoixAnim.quartic, ChoixAnim.quintic,
ChoixAnim.fonction_palier).
Quand un choix est effectué par le sélecteur, le contrôle Image x_img_inter affiche
l’image de la représentation de l’interpolation choisie. Quand survient un clic sur le
bouton x_btn_anim, le storyboard correspondant à l’animation est chargé depuis le
dictionnaire des ressources du x_grid_root dans lequel il est stocké, puis il est exécuté. On
se contente d’animer uniquement la valeur de la propriété OffsetX de la transformation
x_cube_translate. Comme c’est une propriété de type double, on utilisera une piste
d’animation de type DoubleAnimation. Pour cibler cette transformation, l’animation
aura une propriété attachée Storyboard.TargetName = «x_cube_translate». Pour cibler
la propriété OffsetX de cette transformation, l’animation aura une propriété attachée
Storyboard.TargetProperty = «(TranslateTransform3D.OffsetX)». A chaque fois, la valeur
de la propriété OffsetX passera de la valeur 0 à 2.5 par incrément de 0.1 pour une
chronologie de 5 secondes. Le storyboard k_stb_classique effectue le rendu d’une
animation classique comme on a déjà pu le voir auparavant.
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_classiqueˈ>
<DoubleAnimation Storyboard.TargetName=ˈx_cube_translateˈ
Storyboard.TargetProperty=ˈ(TranslateTransform3D.OffsetX)ˈ From=ˈ0ˈ To=ˈ2.5ˈ
By=ˈ0.1ˈ Duration=ˈ00:00:05ˈ AutoReverse=ˈFalseˈ>
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
Le storyboard k_stb_sineease effectue une animation réaliste selon une fonction
Copyright 2013 Patrice REY
EasingMode que l’on fixe à EaseInOut et sa propriété Exponent que l’on fixe à 12
(propriété qui fixe la valeur de l’exposant).
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_exponenteaseˈ>
<DoubleAnimation Storyboard.TargetName=ˈx_cube_translateˈ
Storyboard.TargetProperty=ˈ(TranslateTransform3D.OffsetX)ˈ From=ˈ0ˈ To=ˈ2.5ˈ
By=ˈ0.1ˈ Duration=ˈ00:00:05ˈ AutoReverse=ˈFalseˈ>
<DoubleAnimation.EasingFunction>
<ExponentialEase EasingMode=ˈEaseInOutˈ Exponent=ˈ12ˈ></ExponentialEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
Le storyboard k_stb_powerease effectue une animation réaliste selon une fonction
d’accélération de type PowerEase. L’objet PowerEase a sa propriété EasingMode que
l’on fixe à EaseInOut et sa propriété Power que l’on fixe à 3 (propriété qui fixe la valeur
de l’exposant de la puissance).
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_powereaseˈ>
<DoubleAnimation Storyboard.TargetName=ˈx_cube_translateˈ
Storyboard.TargetProperty=ˈ(TranslateTransform3D.OffsetX)ˈ From=ˈ0ˈ To=ˈ2.5ˈ
By=ˈ0.1ˈ Duration=ˈ00:00:05ˈ AutoReverse=ˈFalseˈ>
<DoubleAnimation.EasingFunction>
<PowerEase EasingMode=ˈEaseInOutˈ Power=ˈ3ˈ></PowerEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
Le storyboard k_stb_quadraticease effectue une animation réaliste selon une fonction
d’accélération de type QuadraticEase. L’objet QuadraticEase a sa propriété EasingMode
que l’on fixe à EaseInOut.
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_quadraticeaseˈ>
<DoubleAnimation Storyboard.TargetName=ˈx_cube_translateˈ
Storyboard.TargetProperty=ˈ(TranslateTransform3D.OffsetX)ˈ From=ˈ0ˈ To=ˈ2.5ˈ
By=ˈ0.1ˈ Duration=ˈ00:00:05ˈ AutoReverse=ˈFalseˈ>
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode=ˈEaseInOutˈ></QuadraticEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
Le storyboard k_stb_quarticease effectue une animation réaliste selon une fonction
d’accélération de type QuarticEase. L’objet QuarticEase a sa propriété EasingMode que
364 Programmez des jeux vidéo 3D avec C#5 et WPF
l’on fixe à EaseInOut.
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_quarticeaseˈ>
<DoubleAnimation Storyboard.TargetName=ˈx_cube_translateˈ
Storyboard.TargetProperty=ˈ(TranslateTransform3D.OffsetX)ˈ From=ˈ0ˈ To=ˈ2.5ˈ
By=ˈ0.1ˈ Duration=ˈ00:00:05ˈ AutoReverse=ˈFalseˈ>
<DoubleAnimation.EasingFunction>
<QuarticEase EasingMode=ˈEaseInOutˈ></QuarticEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
Le storyboard k_stb_quinticease effectue une animation réaliste selon une fonction
d’accélération de type QuinticEase. L’objet QuinticEase a sa propriété EasingMode que
l’on fixe à EaseInOut.
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_quinticeaseˈ>
<DoubleAnimation Storyboard.TargetName=ˈx_cube_translateˈ
Storyboard.TargetProperty=ˈ(TranslateTransform3D.OffsetX)ˈ From=ˈ0ˈ To=ˈ2.5ˈ
By=ˈ0.1ˈ Duration=ˈ00:00:05ˈ AutoReverse=ˈFalseˈ>
<DoubleAnimation.EasingFunction>
<QuinticEase EasingMode=ˈEaseInOutˈ></QuinticEase>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
Le storyboard k_stb_fonction_palier effectue une animation réaliste selon une fonction
d’accélération personnalisée. On inscrit en XAML le code de l’animation.
<Grid.Resources>
<Storyboard x:Key=ˈk_stb_fonction_palierˈ>
<DoubleAnimation x:Name=ˈx_anim_palierˈ Storyboard.TargetName=ˈx_cube_translateˈ
Storyboard.TargetProperty=ˈ(TranslateTransform3D.OffsetX)ˈ From=ˈ0ˈ To=ˈ2.5ˈ
By=ˈ0.1ˈ Duration=ˈ00:00:05ˈ AutoReverse=ˈFalseˈ>
</DoubleAnimation>
</Storyboard>
</Grid.Resources>
Copyright 2013 Patrice REY
Nous allons créer une fonction d’accélération dite «par palier», qui aura comme
résultat de faire avancer le cube d’une certaine quantité durant un temps donné puis
d’arrêter son déplacement durant le temps suivant, et cela un certain nombre de fois.
La figure 8.24 visualise cette fonction d’accélération par palier, qui compte une suite
de 4 enchaînements déplacement/arrêt. Cette séquence de 4 enchaînements est
une séquence par défaut. Une modification de propriété permet d’augmenter ou de
diminuer la séquence.
CHAPITRE 8 Bonification du jeu par l’animation 365
FIGURE 8.24
Valeurs
= f(t) mode EaseIn
Temps t
}
... } //end class
Maintenant que la fonction d’accélération FonctionPalier est implémentée, on instancie
un objet FonctionPalier nommé fonction_palier. On choisit son mode voulu par la
propriété EasingMode (que l’on fixe ici par exemple à EaseIn). La propriété NombrePalier
détermine le nombre des paliers, et la propriété SeuilPalier détermine la durée d’un
palier. Pour affecter cette fonction d’accélération fonction_palier à la piste d’animation
DoubleAnimation, on instancie une variable anim_palier, de type DoubleAnimation,
qui récupère la piste d’animation depuis le Storyboard k_stb_fonction_palier (cette
piste est le premier et unique enfant du storyboard par sa propriété Children qui est
une collection générique de Timeline). Et on affecte à sa propriété EasingFunction la
fonction d’accélération fonction_palier.
stb_anim = (Storyboard)x_grid_root.Resources[«k_stb_fonction_palier»];
FonctionPallier fonction_palier = new FonctionPallier();
fonction_palier.EasingMode = EasingMode.EaseIn;
fonction_palier.NombrePalier = 4;
fonction_palier.SeuilPalier = 0.25;
DoubleAnimation anim_palier = (DoubleAnimation)stb_anim.Children[0];
anim_palier.EasingFunction = fonction_palier;
Une boucle de rendu image par image est ajoutée par la méthode statique
CompositionTarget.Rendering pour l’événement Rendering. La balle va rouler le long
des côtés du plateau, et cela parallèlement aux bords du plateau, en suivant le sens
contraire des aiguilles d’une montre. Quand la balle entre en collision avec un cylindre,
elle prend la direction du bord adjacent et elle suit de nouveau le bord adjacent
parallèlement. On introduit donc une énumération Direction dont les valeurs sont:
• Direction.bas_gauche_vers_bas_droit quand la balle se dirige dans la direction du
cylindre en bas à gauche vers le cylindre en bas à droite,
• Direction.bas_droit_vers_haut_droit quand la balle se dirige dans la direction du
cylindre en bas à droite vers le cylindre en haut à droite,
• Direction.haut_droit_vers_haut_gauche quand la balle se dirige dans la direction du
cylindre en haut à droite vers le cylindre en haut à gauche,
• Direction.haut_gauche_vers_bas_gauche quand la balle se dirige dans la direction du
cylindre en haut à gauche vers le cylindre en bas à gauche.
Au départ de l’animation, la balle se trouve à droite du cylindre x_cyl_bas_gauche. La
variable v_direction, qui stocke l’énumération de déplacement en cours, est initialisée
à Direction.bas_gauche_vers_bas_droit. Le gestionnaire de l’événement Click sur le
bouton x_btn_jouer consiste à démarrer l’horloge, à lancer le rendu image par image,
et à mettre à jour l’état de fonctionnalité des boutons. Le gestionnaire de l’événement
Click sur le bouton x_btn_stopper consiste à suspendre l’horloge, à suspendre le rendu
image par image, et à mettre à jour l’état de fonctionnalité des boutons.
//evenement clic sur btn jouer animation
private void x_btn_jouer_Click(object sender, RoutedEventArgs e) {
x_btn_jouer.IsEnabled = false;
x_btn_stopper.IsEnabled = true;
v_stopwatch.Start();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
//evenement clic sur btn stopper animation
private void x_btn_stopper_Click(object sender, RoutedEventArgs e) {
x_btn_jouer.IsEnabled = true;
x_btn_stopper.IsEnabled = false;
v_stopwatch.Stop();
CompositionTarget.Rendering -= CompositionTarget_Rendering;
}
La balle va se déplacer dans le plan OXZ avec une vitesse dont la composante suivant
l’axe X est v_vitesse_x et dont la composante suivant l’axe Z est v_vitesse_z (ce principe
a déjà été vu). Le taux estimé pour le déplacement est fourni par v_vitesse_taux = 0.01.
Les variables, v_deplac_x et v_deplac_z, sont affectées des valeurs à appliquer pour la
translation de la balle sur la scène. La méthode DeterminerVitesse permet, en fonction
370 Programmez des jeux vidéo 3D avec C#5 et WPF
de la variable de direction v_direction, de fixer les vitesses de déplacement de la balle
en fonction de l’endroit où la balle se trouve sur un bord à un moment donné.
//
private void DeterminerVitesse() {
switch (v_direction) {
case Direction.bas_gauche_vers_bas_droit:
v_vitesse_x = v_vitesse_taux;
v_vitesse_z = 0;
break;
case Direction.bas_droit_vers_haut_droit:
v_vitesse_x = 0;
v_vitesse_z = -v_vitesse_taux;
break;
case Direction.haut_droit_vers_haut_gauche:
v_vitesse_x = -v_vitesse_taux;
v_vitesse_z = 0;
break;
case Direction.haut_gauche_vers_bas_gauche:
v_vitesse_x = 0;
v_vitesse_z = v_vitesse_taux;
break;
}
}
De plus, pour simuler le roulement de la balle, il va falloir lui appliquer une rotation
de façon à ce qu’elle tourne autour d’un axe de rotation qui passe par le centre de la
sphère, avec un angle de rotation défini. La variable v_rot_angle sera l’angle de rotation
à effectuer pour la rotation, et cela en fonction d’un pas d’angle dont l’incrément est
v_rot_taux_angle défini à 5 degrés.
On ajoute donc une méthode TSphere.Appliquer qui devra effectuer le déplacement
complet de la balle (translation et rotation), et qui reçoit en paramètre:
• les valeurs suiv_x, suiv_y et suiv_z, de type double, qui indiquent les valeurs de
translation à effectuer,
• la valeur vecteur_axe, de type Vector3D, qui représente l’orientation de l’axe de
rotation à utiliser,
• la valeur angle, de type double, qui représente la valeur de l’angle de rotation.
Copyright 2013 Patrice REY
Sc
X
vecteur_axe = Vector3D(0,0,-1)
v_rot_angle > 0
Direction.bas_gauche_vers_bas_droit
v_rot_angle > 0
FIGURE 8.28
Direction.bas_droit_vers_haut_droit
1 2
v_rot_angle > 0
v_rot_angle > 0
vecteur_axe = Vector3D(-1,0,0)
Quand le déplacement (figure 8.29) s’effectue le long du bord en allant du cylindre haut
droit vers le cylindre haut gauche (Direction_haut_droit_vers_haut_gauche), le vecteur
représentant l’axe de rotation a pour direction l’axe Z positif, en passant par le centre
de la sphère, et pour un angle de rotation positif.
FIGURE
8.29 1 v_rot_angle > 0
v_rot_angle > 0
CHAPITRE 8 Bonification du jeu par l’animation 375
1 2
v_rot_angle > 0
vecteur_axe = Vector3D(1,0,0)
v_rot_angle > 0
Rp) et un cylindre (de rayon Rc pour la face externe) dont les dimensions de la boite
englobante sont identiques. Dans ce cas précis, il y a collision (repère 2) si la distance D,
égale à la distance entre les points Sp et Sc, est inférieure ou égale à la somme Rp+Rc.
Si on prend un cylindre dont la hauteur de la boite englobante est bien supérieure
à la hauteur de la boite englobante de la sphère, la distance Rp+Rc constituera une
bonne appréciation de cette collision (cas de notre animation). Comme, pour nos deux
objets 3D, nous avons accès en permanence au calcul de la boite englobante par la
CHAPITRE 8 Bonification du jeu par l’animation 377
Rp : rayon Rc : rayon de la
Sp Rp Rc
de la face externe du
sphère Sc
cylindre
Sp Rp Rc
distance D = Rp + Rc
Sc
Jusqu’à présent le plateau du jeu avait une forme rectangulaire plane. Nous allons
voir dans ce paragraphe comment modéliser et générer des surfaces de plateau qui
possèdent une élévation (c’est-à-dire une coordonnée y différente de 0 pour des
coordonnées x et z données). La figure 8.32 visualise le résultat obtenu pour une surface
(la surface numéro 5 parmi les 6 proposées plus celle par défaut). Ce projet à part se
trouve dans le dossier 04_VS2012_3d_anim_realiste_3 du code de programmation.
FIGURE 8.32
380 Programmez des jeux vidéo 3D avec C#5 et WPF
Le principe de la génération des surfaces avec élévation consiste à calculer, pour des
coordonnées x et z d’un point, la coordonnée y (qui représente l’élévation) en fonction
d’une équation dont les paramètres sont fonction de x et z. Par exemple, pour des
coordonnées x et z données, on aurait y = 3 * x2 + 2 * z comme équation.
On définit une classe TSolParametre qui hérite de UIElement3D. On lui ajoute les
propriétés P_Modele, P_DivisionX (nombre de division suivant l’axe X, de type int,
valeur par défaut 40), P_DivisionZ (nombre de division selon l’axe Z, de type int, valeur
par défaut 40), P_CouleurUnie (couleur appliquée en l’absence de texture, de type
Color, valeur par défaut Color.Aqua), P_Texture (texture à appliquer, de type Material,
valeur par défaut null), et P_SelecteurSurface (sélecteur de surface en fonction de
l’énumération ChoixSurface, valeur par défaut ChoixSurface.defaut).
public class TSolParametre : UIElement3D {
...
//donnees
public enum ChoixSurface { defaut, surface_1, surface_2, surface_3, surface_4,
surface_5, surface_6 };
//la propriete SelecteurSurface
private static readonly DependencyProperty ProprieteSelecteurSurface =
DependencyProperty.Register(«P_SelecteurSurface»,
typeof(ChoixSurface),
typeof(TSolParametre),
new PropertyMetadata(ChoixSurface.defaut, PM_SelecteurSurface));
private static void PM_SelecteurSurface(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
TSolParametre objet = (TSolParametre)d;
objet.InvalidateModel();
}
public ChoixSurface P_SelecteurSurface {
get { return (ChoixSurface)GetValue(ProprieteSelecteurSurface); }
set { SetValue(ProprieteSelecteurSurface, value); }
}
...
}
La méthode redéfinie OnUpdateModel génère le maillage de l’objet TSolParametre. Si
la propriété P_Texture est différente de null, on applique la texture sur la face avant,
sinon on applique la couleur P_CouleurUnie. La face arrière est peinte en jaune. La
Copyright 2013 Patrice REY
zmin
zmax
Z
ymin
xmin xmax
(i,j) ( i+1 , j )
facette avec ses 4 sommets
( i , j +1) ( i+1 , j+1 )
382 Programmez des jeux vidéo 3D avec C#5 et WPF
private MeshGeometry3D MaillageSurfaceParametree() {
MeshGeometry3D maillage = new MeshGeometry3D();
Vector3D[,] pts = new Vector3D[P_DivisionX, P_DivisionZ];
double xmin = 0, xmax = 0, zmin = 0, zmax = 0, ymin = 0, ymax = 0;
...
double dx = (xmax - xmin) / P_DivisionX;
double dz = (zmax - zmin) / P_DivisionZ;
for (int i = 0; i < P_DivisionX; i++) {
double x = xmin + i * dx;
for (int j = 0; j < P_DivisionZ; j++) {
double z = zmin + j * dz;
pts[i, j] = CalculCoordonneePoint(x, z);
pts[i, j] += new Vector3D(0, 0, 0);
pts[i, j] = Normaliser(pts[i, j], xmin, xmax, ymin, ymax, zmin, zmax);
}
}
...
}
private Vector3D CalculCoordonneePoint(double x, double z) {
Vector3D point3d = new Vector3D(0, 0, 0);
switch (P_SelecteurSurface) {
case ChoixSurface.defaut:
point3d.X = x;
point3d.Y = 0;//elevation nulle par défaut
point3d.Z = z;
break;
}
return point3d;
}
private Vector3D Normaliser(Vector3D v3d, double xmin, double xmax, double ymin,
double ymax, double zmin, double zmax) {
v3d.X = -1 + 2 * (v3d.X - xmin) / (xmax - xmin);
v3d.Y = -1 + 2 * (v3d.Y - ymin) / (ymax - ymin);
v3d.Z = -1 + 2 * (v3d.Z - zmin) / (zmax - zmin);
return v3d;
}
Il faut procéder maintenant à la réalisation des facettes rectangulaires. A partir du
tableau pts, avec deux boucles imbriquées qui délivre les variables i et j en fonction
Copyright 2013 Patrice REY
du nombre de division, on établit les coordonnées des 4 sommets pour une facette:
pt_a = pts[i,j], pt_b = pts[i+1,j], pt_c = pts[i+1,j+1] et pt_d = pts[i,j+1]. Pour établir une
face, on passe les points à la propriété Positions de MeshGeometry3D dans l’ordre
pt_c, pt_b, pt_a et pt_d (sens contraire des aiguilles d’une montre pour indiquer la face
vue) comme le montre la figure 8.34. Et on passe à la propriété TextureCoordinates de
MeshGeometry3D les coordonnées de texture à appliquer en fonction d’une image
graphique 2D (on s’aide d’un compteur cpt_div_h pour une division horizontale et d’un
CHAPITRE 8 Bonification du jeu par l’animation 383
8.36 repère 2). La surface ChoixSurface.surface_5 représente une surface avec xmin=-2,
xmax=+2, zmin=-2, zmax=+2, ymin=-5, ymax=+5 et l’équation y = cos(x2 + z2) * sin(x)
(figure 8.36 repère 3). La surface ChoixSurface.surface_6 représente une surface avec
xmin=-10, xmax=+10, zmin=-10, zmax=+10, ymin=-0.85, ymax=+0.85 (figure 8.36
repère 4).
FIGURE 8.36
1 2
3 4
Dans le logiciel MAYA, en version 2013, on modélise un simple maillage représenté par
un cube (figure 8.37). La base du cube a une dimension de 2 unités suivant l’axe X et
l’axe Z. La hauteur du cube a une dimension de 1 unité suivant l’axe Y.
FIGURE 8.37
Pour exporter au format OBJ dans MAYA, il faut activer le plug-in d’exportation. Dans
le menu Windows, puis Setting preferences, il faut sélectionner la rubrique Plug-in
Copyright 2013 Patrice REY
manager (figure 8.38 repère 1). Une fenêtre s’ouvre, intitulée Plug-in manager. Une
liste des formats d’importation et d’exportation s’affiche. A la ligne où est inscrit le
nom du format objExport.mll, il faut cocher les cases Loaded et Auto Load (figure 8.38
repère 2). Après avoir refermé la fenêtre, sélectionnez le cube en cliquant sur une de
ses arêtes et choisissez dans le menu File la rubrique Export selection. Dans la fenêtre
d’exportation qui s’ouvre, donnez un nom comme export_cube, sélectionnez dans la
CHAPITRE 8 Bonification du jeu par l’animation 387
1
2
Le format OBJ est un format de fichier contenant la description d’une géométrie 3D. Il
a été défini par la société Wavefront Technologies dans le cadre du développement de
son logiciel d’animation Advanced Visualizer. Ce format de fichier est ouvert et a été
adopté par d’autres logiciels 3D (tels que 3D Turbo, Poser, Maya, Blender, MeshLab, 3D
Studio Max, Lightwave, etc) pour des traitements d’importation et d’exportation de
données 3D. Les formes géométriques peuvent être définies par des polygones ou des
surfaces lisses telles que des surfaces rationnelles et non rationnelles.
388 Programmez des jeux vidéo 3D avec C#5 et WPF
Les fichiers OBJ sont au format ASCII (une version binaire existe, identifiée par
l’extension MOD). Un commentaire peut être placé en faisant commencer la ligne
par le caractère #. Une surface polygonale est décrite par un ensemble de sommets
(accompagné de coordonnées de texture et de normales en chaque sommet) et d’un
ensemble de faces. Un sommet est défini de la manière suivante: v 1.023 0.056 0.045
avec l’indicateur v (comme vertex) indiquant qu’il s’agit d’un sommet, puis une série
de 3 nombres avec une précision de chiffres après la virgule. Une coordonnée de
texture est définie de la manière suivante: vt 1.02 0.04 avec l’indicateur vt et une
série de 2 nombres. Une normale est définie de la manière suivante: vn 0.0 1.0 0.0
avec l’indicateur vn et une série de 3 nombres. Chaque face est ensuite définie par un
ensemble d’indices faisant référence aux coordonnées des points, de texture et des
normales définies précédemment. Par exemple, pour la face suivante f v1/vt1/vn1 v2/
vt2/vn2 v3/vt3/vn3, f est l’indicateur d’une face (pouvant être avec 3 sommets pour
un triangle ou bien 4 sommets pour deux triangles collés), et cette face est définie
un triangle constitué des sommets d’indices v1, v2 et v3 dans la liste des sommets v.
Chacun de ces sommets possède une coordonnée de texture identifiée par son indice
dans la liste des coordonnées de texture vt et une normale identifiée dans la liste des
normales vn.
Lorsque plusieurs objets cohabitent dans le même fichier, la section définissant l’objet
est définie par o [nom de l’objet]. Lorsque plusieurs groupes de faces cohabitent dans
le même objet, la section définissant chaque groupe est définie par g [nom du groupe].
Des matériaux peuvent être référencés en important des fichiers .mtl (Material Template
Library) représentés par la séquence usemtl [nom de matériau].
Par exemple, si on ouvre dans un éditeur de texte notre fichier export_cube.obj, on
obtient le contenu ci-dessous:
# This file uses centimeters as units for non-parametric coordinates.
mtllib export_cube.mtl
g default
v -0.986112 -0.000000 0.971138
v 0.985169 -0.000000 0.971138
v -0.986112 1.051467 0.971138
Copyright 2013 Patrice REY
vt 0.375000 0.500000
vt 0.625000 0.500000
vt 0.375000 0.750000
vt 0.625000 0.750000
vt 0.375000 1.000000
vt 0.625000 1.000000
vt 0.875000 0.000000
vt 0.875000 0.250000
vt 0.125000 0.000000
vt 0.125000 0.250000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
s off
g pCube1
usemtl initialShadingGroup
f 1/1/1 2/2/2 4/4/3 3/3/4
f 3/3/5 4/4/6 6/6/7 5/5/8
f 5/5/9 6/6/10 8/8/11 7/7/12
f 7/7/13 8/8/14 2/10/15 1/9/16
f 2/2/17 8/11/18 6/12/19 4/4/20
f 7/13/21 1/1/22 3/3/23 5/14/24
On voit bien que le fichier contient 8 indicateurs de points v, et un ensemble d’indicateurs
de texture vt et de normales vn. De plus il y a 6 faces d’indicateur f, composées de 4
points (2 triangles collés). A noter que l’ordre des points pour les faces est celui du sens
contraire des aiguilles d’une montre.
390 Programmez des jeux vidéo 3D avec C#5 et WPF
Maintenant nous allons procéder à l’importation du maillage d’un cube qui a été
exporté depuis MAYA au format OBJ. Une fois le maillage importé, nous appliquerons
notre texture au maillage (une texture représentant du bois). La figure 8.40 montre le
résultat obtenu après importation.
Sur la grille principale x_grid_root contenant la scène 3D x_viewport, nous positionnons
une grille x_grid_infos qui va afficher la lecture des données d’importation (figure 8.41)
avant de modéliser un objet 3D à partir de ce maillage d’importation. Tant qu’à la
scène, elle est composée d’un plateau TSol x_sol, texturé avec une image, et un repère
3D TRepere x_repere pour pouvoir visualiser plus facilement l’implantation du cube
après importation.
FIGURE 8.41
<ContainerUIElement3D>
<!-- repere -->
<modele3d:TRepere x:Name=ˈx_repereˈ P_LongueurDemiAxe=ˈ4ˈ></modele3d:TRepere>
<!-- sol -->
<modele3d:TSol x:Name=ˈx_solˈ P_LargeurXNeg=ˈ3ˈ P_LargeurXPos=ˈ3ˈ
P_ProfondeurZPos=ˈ3ˈ P_ProfondeurZNeg=ˈ3ˈ>
...
</modele3d:TSol>
</ContainerUIElement3D>
FIGURE 8.40
392 Programmez des jeux vidéo 3D avec C#5 et WPF
La classe TFormatObj déclarée va servir à récupérer un fichier ASCII contenant des
données au format OBJ, et permettre de traiter ces données pour créer un maillage.
On ajoute un constructeur avec paramètre qui reçoit le chemin absolu pointant sur le
fichier ASCII à traiter. Pour cela, on utilise un objet StreamReader qui charge le fichier
avec un encodage ASCII par l’énumération Encoding.ASCII. La méthode ReadToEnd
permet de lire le contenu texte en entier, et à l’affecter à la variable v_contenu_texte
accessible par la propriété P_ContenuTexte. Un bloc try..catch permet de lever une
erreur en cas de problème d’ouverture de fichier.
public class TFormatObj {
//constructeur
public TFormatObj(string chemin_fichier) {
try {
using (StreamReader sr = new StreamReader(chemin_fichier, Encoding.ASCII)) {
v_contenu_texte = sr.ReadToEnd();
}
}
catch (Exception e) {
MessageBox.Show(«erreur: « + e.Message);
}
}
...
}
Avec la méthode TraiterLeFichier, on découpe le contenu texte ligne par ligne en
détectant le caractère de retour chariot (\u000A) de fin de ligne. On ajoute à une liste
List<string> v_liste_ligne toutes les lignes de texte contenues et non vides. La méthode
Trim permet de supprimer l’espace présent en début de ligne et en fin de ligne. Cette
liste est accessible par la propriété P_ListeLigne.
public class TFormatObj {
...
//traiter le fichier
public void TraiterLeFichier() {
string[] lignes = v_contenu_texte.Split(new char[] { ‘\u000A’ });
v_liste_ligne = new List<string>();
for (int xx = 0; xx < lignes.Length; xx++) {
if (lignes[xx].Length != 0) {
Copyright 2013 Patrice REY
v_liste_ligne.Add(lignes[xx].Trim());
}
}
}
...
}
La méthode ExtraireSommets permet de charger la liste des sommets, de type Point3D,
présents dans le fichier. Une boucle for permet de lire chaque ligne contenue dans la
CHAPITRE 8 Bonification du jeu par l’animation 393
liste. Une ligne est ensuite découpée en mots en utilisant le caractère séparateur espace
(‘ ‘). Si le premier mot contient la chaîne unique v, cela veut dire qu’on est en présence
d’un sommet. Donc les trois chaînes suivantes sont les représentations numériques
des coordonnées x, y et z du Point3D. A noter l’utilisation de la méthode Replace pour
remplacer le point décimal par une virgule décimale avant l’utilisation de la méthode
double.Parse, qui instancie une valeur double à partir d’une chaîne textuelle. Tous les
points Point3D générés sont ajoutés à une collection générique Point3DCollection v_
collect_p3d. Cette collection est accessible par la propriété P_CollectionPoint3D.
public class TFormatObj {
...
//recuperer les sommets Point3D
public void ExtraireSommets() {
v_collect_p3d = new Point3DCollection();
for (int xx = 0; xx < v_liste_ligne.Count; xx++) {
string[] decoupe = v_liste_ligne[xx].Split(new char[] { ‘ ‘ });
//sommets
if (decoupe[0] == «v») {
//on remplace le point decimal par la virgule decimale
string s_res_x = decoupe[1].Replace(‘.’, ‘,’);
string s_res_y = decoupe[2].Replace(‘.’, ‘,’);
string s_res_z = decoupe[3].Replace(‘.’, ‘,’);
Point3D p3d = new Point3D();
p3d.X = double.Parse(s_res_x);
p3d.Y = double.Parse(s_res_y);
p3d.Z = double.Parse(s_res_z);
v_collect_p3d.Add(p3d);
}
}
}
...
}
La méthode ExtraireCoordonneeTexture permet de charger la liste des coordonnées de
texture, de type Point. L’indicateur de coordonnées 2D de texture est vt. On procède
de même par un changement du point décimal en virgule décimale, puis en instanciant
des valeurs affectées aux propriétés X et Y d’un objet Point par la méthode double.
Parse appliquée à une chaîne textuelle.
Tous les points générés, de type Point, sont ajoutés à une collection générique
PointCollection v_collect_pd. Cette collection est accessible par la propriété P_
CollectionPointTexture.
public class TFormatObj {
...
//recuperer les coordonnées de texture Point
public void ExtraireCoordonneeTexture() {
394 Programmez des jeux vidéo 3D avec C#5 et WPF
v_collect_pd = new PointCollection();
for (int xx = 0; xx < v_liste_ligne.Count; xx++) {
string[] decoupe = v_liste_ligne[xx].Split(new char[] { ‘ ‘ });
//sommets
if (decoupe[0] == «vt») {
//on remplace le point decimal par la virgule decimale
string s_res_x = decoupe[1].Replace(‘.’, ‘,’);
string s_res_y = decoupe[2].Replace(‘.’, ‘,’);
Point pt = new Point();
pt.X = double.Parse(s_res_x);
pt.Y = double.Parse(s_res_y);
v_collect_pd.Add(pt);
}
}
}
...
}
La méthode ExtraireNormales permet de charger la liste des coordonnées des normales
aux sommets, de type Vector3D. L’indicateur des normales est vn. On procède de
même par un changement du point décimal en virgule décimale, puis en instanciant
des valeurs affectées aux propriétés X, Y et Z d’un objet Vector3D par la méthode
double.Parse appliquée à une chaîne textuelle.
Tous les vecteurs générés, de type Vector3D, sont ajoutés à une collection générique
Vector3DCollection v_collect_normale. Cette collection est accessible par la propriété
P_CollectionNormale.
public class TFormatObj {
...
//recuperer les normales Vector3D
public void ExtraireNormales() {
v_collect_normale = new Vector3DCollection();
for (int xx = 0; xx < v_liste_ligne.Count; xx++) {
string[] decoupe = v_liste_ligne[xx].Split(new char[] { ‘ ‘ });
//sommets
if (decoupe[0] == «vn») {
//on remplace le point decimal par la virgule decimale
string s_res_x = decoupe[1].Replace(‘.’, ‘,’);
string s_res_y = decoupe[2].Replace(‘.’, ‘,’);
Copyright 2013 Patrice REY
maillage.TriangleIndices.Add(depart + 2);
maillage.TriangleIndices.Add(depart + 2);
maillage.TriangleIndices.Add(depart + 3);
maillage.TriangleIndices.Add(depart);
depart += 4;
}
}
...
}
Pour ajouter le modèle qui représente le maillage importé, il suffit de créer une classe
TFormatObjModele qui hérite de UIElement3D. On ajoute un constructeur avec
paramètre qui reçoit un maillage MeshGeometry.
public class TFormatObjModele : UIElement3D {
//
private MeshGeometry3D v_maillage_3d = null;
//constructeur
public TFormatObjModele(MeshGeometry3D maillage_3d) {
v_maillage_3d = maillage_3d;
}
...
}
La propriété P_CouleurUnie fixe une couleur unie à appliquer au modèle en l’absence
d’une texture (par défaut). La propriété P_Texture fixe une texture à appliquer au
modèle dès qu’elle est présente. Dans la méthode héritée redéfinie, le maillage est
affecté à la propriété Geometry de l’objet GeometryModel3D, qui lui-même est affecté
à la propriété TFormatObjModele.Visual3DModel.
public class TFormatObjModele : UIElement3D {
...
//redefinition de la methode de rendu
protected override void OnUpdateModel() {
GeometryModel3D geo = new GeometryModel3D();
geo.Geometry = v_maillage_3d;
if (this.P_Texture == null) {
geo.Material = new DiffuseMaterial(new SolidColorBrush(this.P_CouleurUnie));
}
else {
geo.Material = this.P_Texture;
}
geo.BackMaterial = new DiffuseMaterial(new SolidColorBrush(Colors.Yellow));
this.Visual3DModel = geo;
}
}
Pour ajouter l’objet importé à la scène 3D, on instancie un objet TFormatObjModele,
nommé modele_import, qui reçoit en paramètre le chemin absolu vers le fichier exporté
398 Programmez des jeux vidéo 3D avec C#5 et WPF
par MAYA. On lui ajoute une texture par la propriété P_Texture. Et cet objet est ajouté
à la scène par la propriété Children de x_conteneur_3d.
//clic sur x_btn_charge_ok
private void x_btn_charge_ok_Click(object sender, RoutedEventArgs e) {
x_grid_infos.Visibility = Visibility.Hidden;
TFormatObjModele modele_import = new TFormatObjModele(v_fichier_obj.P_Maillage3D);
ImageBrush pinceau_image = new ImageBrush();
pinceau_image.ImageSource = new BitmapImage(new Uri(«pack://application:,,/
contenu/image_texture/texture_cube_import.png», UriKind.Absolute));
DiffuseMaterial diffus = new DiffuseMaterial(pinceau_image);
modele_import.P_Texture = diffus;
x_conteneur_3d.Children.Add(modele_import);
}
Au sommaire de ce chapitre
• la prise en charge de l’audio avec le contrôle MediaElement
• les manipulations d’un fichier audio (lecture, pause, arrêt, positionnement
de la tête de lecture)
• l’utilisation de l’objet DispatcherTimer (minuterie)
• le contrôle du volume et de la balance d’une piste sonore
• la façon d’intégrer des pistes sonores dans un jeu pour apporter une
touche de réalisme et de convivialité supplémentaire
400 Programmez des jeux vidéo 3D avec C#5 et WPF
Tous les jeux vidéo possèdent une sonorisation. Cela permet de rendre encore plus
vivant le jeu. Dans ce chapitre vous verrez comment lire les fichiers audio avec leurs
différents formats au travers d’une application autonome pratique. Ecouter une
piste sonore et savoir comment obtenir ses caractérisques (durée, etc.) sont des
manipulations courantes à connaître. Et nous terminerons par une intégration de
pistes sonores dans le jeu.
WPF prend en charge un certain nombre de format audio comme par exemple le
format WAV, le format WMA (Windows Media Audio) et le format MP3 (ISO MPEG-1
Layer III). Nous allons voir l’utilisation du contrôle MediaElement qui est le contrôle qui
prend en charge l’audio et la vidéo.
La classe MediaElement représente un objet qui contient des données audio, vidéo
ou les deux. Un contrôle MediaElement peut lire plusieurs types de médias audio et
vidéo. Un MediaElement est fondamentalement une région rectangulaire qui peut
afficher de la vidéo sur sa surface ou lire un contenu audio (auquel cas aucune vidéo
n’est affichée, mais le MediaElement joue encore un rôle d’objet lecteur avec les API
appropriées). Dans la mesure où il s’agit d’un UIElement, un MediaElement prend
en charge des opérations d’entrée, telles que les événements de souris et du clavier,
et peut capturer le focus ou la souris. La figure 9.1 visualise l’arbre d’héritage d’un
MediaElement.
La classe MediaElement hérite de FrameworkElement, et elle est donc compatible
avec le système de disposition. Cependant, son comportement visuel est particulier
car il dépend du flux chargé. Un objet MediaElement permet de diffuser les deux types
de flux, audio et vidéo. Les propriétés booléennes HasAudio et HasVideo indiquent la
nature du flux chargé. Quand il diffuse un flux audio, l’objet MediaElement n’a pas de
Copyright 2013 Patrice REY
Nous allons réaliser une application simple et autonome pour bien s’imprégner des
Copyright 2013 Patrice REY
FIGURE 9.2
2
404 Programmez des jeux vidéo 3D avec C#5 et WPF
La figure 9.3 visualise la composition et la disposition des divers éléments en XAML. Le
sélecteur x_select permet de choisir une piste sonore parmi trois fichiers sonores. La
zone x_text_infos affiche des informations relatives au chargement de la piste sonore.
Les champs x_text_duree_totale et x_text_duree_encours affichent les durées relatives à la
piste sonore. Le volume sonore et la balance sonore sont modifiables par les glissières
x_slider_volume et x_slider_balance. Le rectangle x_rect_piste affiche une progression
visuelle de la piste sonore lue. Les boutons x_img_lecture, x_img_pause et x_img_arret
permettent de contrôler la piste sonore.
FIGURE 9.3
ComboBox x_select
TextBlock x_text_infos
TextBlock x_text_duree_totale
TextBlock x_text_duree_encours
Rectangle x_rect_piste
Slider x_slider_volume
Slider x_slider_balance
FIGURE 9.4
AfficherInfos(«choix: « + chemin_fichier_mp3);
v_fichier_audio = chemin_fichier_mp3;
x_media_elem_xaml.Source = new Uri(doss_exe + «/media_audio/» + v_fichier_audio,
UriKind.Absolute);
x_media_elem_xaml.Volume = 0.5;
x_media_elem_xaml.MediaOpened += x_media_elem_xaml_MediaOpened;
x_media_elem_xaml.MediaFailed += x_media_elem_xaml_MediaFailed;
x_media_elem_xaml.MediaEnded += x_media_elem_xaml_MediaEnded;
x_media_elem_xaml.Play(); }
CHAPITRE 9 Sonorisation du jeu 407
FIGURE 9.5
DispatcherTimer
v_dispatcher
intervalle de
0.1 seconde
Chaque fois qu’un relevé est effectué, on note la position de la tête de lecture (propriété
CHAPITRE 9 Sonorisation du jeu 409
Rectangle
x_rect_piste
1 x_rect_piste.Width
v_rect_piste_long_maxi
DispatcherTimer
v_dispatcher
2 intervalle de
0.1 seconde
0 1
-1 +1
//
private void x_slider_volume_ValueChanged(object sender,
Copyright 2013 Patrice REY
RoutedPropertyChangedEventArgs<double> e) {
x_media_elem_xaml.Volume = x_slider_volume.Value;
}
//
private void x_slider_balance_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e) {
x_media_elem_xaml.Balance = x_slider_balance.Value;
}
CHAPITRE 9 Sonorisation du jeu 411
Quand la piste sonore est en cours de lecture, elle peut être mise en pause (figure 9.7
repère 1) ou mise à l’arrêt (figure 9.7 repère 4). Quand la piste sonore est en pause,
elle peut être mise de nouveau en lecture (figure 9.7 repère 2) ou mise à l’arrêt (figure
9.7 repère 4).
FIGURE 9.7
//btn lecture
private void x_img_lecture_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e) {
DesactiverBouton(x_img_lecture);
x_media_elem_xaml.Play();
ActiverBouton(x_img_pause);
ActiverBouton(x_img_arret);
}
412 Programmez des jeux vidéo 3D avec C#5 et WPF
La figure 9.8 visualise les différentes étapes de l’activité et de l’inactivité des trois
boutons en fonction de l’utilisation de la piste sonore (lecture, arrêt et pause).
FIGURE 9.8
piste sonore à l’arrêt
//btn pause
private void x_img_pause_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e) {
DesactiverBouton(x_img_pause);
x_media_elem_xaml.Pause();
ActiverBouton(x_img_lecture);
ActiverBouton(x_img_arret);
}
//btn arret
private void x_img_arret_MouseLeftButtonDown(object sender,
MouseButtonEventArgs e) {
DesactiverBouton(x_img_arret);
x_media_elem_xaml.Stop();
ActiverBouton(x_img_lecture);
DesactiverBouton(x_img_pause);
}
Maintenant, nous allons terminer en ajoutant des pistes sonores au jeu (dossier
05_VS2012_3d_c5_wpf45_jeu dans le code de programmation). Au lancement du jeu
(figure 9.9 repère 1), on a l’écran d’attente. Durant cette étape, on va jouer en boucle
la piste sonore intitulée musique_fond_00m56.mp3. A noter que toutes les pistes
sonores utilisées sont ajoutées dans un dossier nommé media_audio, et comme on l’a
déjà vu, à chaque piste audio, on lui affecte la valeur Content pour Build action et la
valeur Copy always dans Copy in output directory (fenêtre des propriétés). La variable
MediaElement v_audio_attente charge le fichier sonore, ses propriétés LoadedBehavior
et UnloadedBahavior sont fixées à Manual, et sa propriété Volume est fixée à 0.5. Avec
la méthode Play, on démarre la piste sonore. Et pour qu’elle joue en boucle, il faut
lui ajouter un gestionnaire pour l’événement MediaEnded dans lequel on applique la
méthode Stop à la piste sonore (ce qui entraîne le positionnement de la tête de lecture
au début) puis la méthode Play (qui joue la piste sonore à partir de l’emplacement
courant de la tête de lecture).
private void Window_Loaded(object sender, RoutedEventArgs e) {
...
v_audio_attente = new MediaElement();
v_audio_attente.Source = new Uri(doss_exe + «/media_audio/musique_fond_00m56.
mp3»,UriKind.Absolute);
v_audio_attente.LoadedBehavior = MediaState.Manual;
v_audio_attente.UnloadedBehavior = MediaState.Manual;
v_audio_attente.Volume = 0.5;
v_audio_attente.Play();
v_audio_attente.MediaEnded += v_piste_attente_MediaEnded; }
FIGURE
9.9
MediaElement
v_audio_attente
(volume = 0.5)
clic bouton
lancer
MediaElement
v_audio_attente
(volume = 0.30)
MediaElement
v_audio_attente
(volume = 0.30)
CHAPITRE 9 Sonorisation du jeu 415
//
private void v_piste_attente_MediaEnded(object sender, RoutedEventArgs e) {
v_audio_attente.Stop();
v_audio_attente.Play();
}
Quand le joueur clique sur le bouton de démarrage du jeu, on baisse le volume de v_
audio_attente à une valeur de 0.5 et on charge le fichier sonore musique_fond_03m00.
mp3 qui sera joué durant la phase propre du jeu. La variable MediaElement v_audio_jeu
stocke cette piste sonore. Le repère 2 de la figure 9.9 visualise les écrans de cette étape.
On ajoute aussi un gestionnaire pour l’événement MediaEnded pour faire boucler la
lecture de la piste sonore v_audio_jeu.
//bouton de lancement du jeu
private void x_btn_lancer_Click(object sender, RoutedEventArgs e) {
...
v_audio_attente.Volume = 0.30;
v_audio_jeu = new MediaElement();
v_audio_jeu.Source = new Uri(doss_exe + «/media_audio/musique_fond_03m00.mp3»,
UriKind.Absolute);
v_audio_jeu.LoadedBehavior = MediaState.Manual;
v_audio_jeu.UnloadedBehavior = MediaState.Manual;
v_audio_jeu.Volume = 0;
v_audio_jeu.MediaEnded += v_audio_jeu_MediaEnded;
}
//
private void v_audio_jeu_MediaEnded(object sender, RoutedEventArgs e) {
v_audio_jeu.Stop();
v_audio_jeu.Play();
}
Et quand le compte à rebours est terminé, on procède à la lecture de la piste sonore
v_audio_jeu par la méthode Play (et en fixant sa propriété Volume à 0.5).
//evenement storyboard arc termine
private void stb_arc_rebours_Completed(object sender, EventArgs e) {
...
v_audio_attente.Stop();
v_audio_jeu.Volume = 0.5;
v_audio_jeu.Play();
}
Quand la fin de jeu arrive, on a une piste sonore applaudissement_00m02.mp3 (durée 2
secondes) à jouer si la partie est gagnée et une piste sonore partie_perdue_00m04.mp3
(durée 4 secondes) à jouer si la partie est perdue.
En fonction de la fin de jeu, une de ces deux pistes est jouée (figure 9.10). La
variable MediaElement v_audio_partie_perdue joue une de ces pistes et la variable
MediaElement v_audio_partie_gagnee joue l’autre piste.
416 Programmez des jeux vidéo 3D avec C#5 et WPF
FIGURE 9.10
fin de jeu
MediaElement
v_audio_partie_perdue
(volume = 0.5)
MediaElement
v_audio_partie_gagnee
(volume = 0.5)
if (v_etat_jeu == EtatJeu.terminer) {
...
Copyright 2013 Patrice REY
if (v_liste_brique.Count != 0) {
message = «Partie perdue !!!»;
v_audio_partie_perdue = new MediaElement();
v_audio_partie_perdue.Source = new Uri(doss_exe +
«/media_audio/partie_perdue_00m04.mp3», UriKind.Absolute);
v_audio_partie_perdue.LoadedBehavior = MediaState.Manual;
v_audio_partie_perdue.UnloadedBehavior = MediaState.Manual;
v_audio_partie_perdue.Volume = 0.5;
v_audio_partie_perdue.Play(); }
CHAPITRE 9 Sonorisation du jeu 417
else {
message = «Partie gagnée avec « + v_score.ToString() + « pts en «;
message += v_stopwatch.Elapsed.Minutes.ToString(«00») + «:»
+ v_stopwatch.Elapsed.Seconds.ToString(«00»);
v_audio_partie_gagnee = new MediaElement();
v_audio_partie_gagnee.Source = new Uri(doss_exe +
«/media_audio/applaudissement_00m02.mp3», UriKind.Absolute);
v_audio_partie_gagnee.LoadedBehavior = MediaState.Manual;
v_audio_partie_gagnee.UnloadedBehavior = MediaState.Manual;
v_audio_partie_gagnee.Volume = 0.5;
v_audio_partie_gagnee.Play();
}
Et quand l’écran de fin de jeu est actif, un clic sur le bouton x_btn_rejouer relance une
nouvelle partie pour laquelle on redémarre une nouvelle séquence de la piste sonore
v_audio_attente. Ne pas oublier d’appliquer la méthode Stop puis la méthode Start pour
lire une piste sonore depuis le début quand elle a été jouée au moins une fois.
//clic sur bouton rejouer une partie
private void x_btn_rejouer_Click(object sender, RoutedEventArgs e) {
...
v_audio_attente.Volume = 0.5;
v_audio_attente.Play();
}
private void stb_arc_rebours_rejouer_Completed(object sender, EventArgs e) {
...
v_audio_attente.Stop();
v_audio_jeu.Volume = 0.5;
v_audio_jeu.Play();
}
INDEX
Index 421
A C
Camera 113
Add 24, 30, 32
caméra 77
AffineTransform3D 90
CenterX 91, 99
AmbientColor 124
CenterY 91, 99
AmbientLight 124, 127
CenterZ 91, 99
Angle 67
centre de projection 72
animation 316
CharAnimationUsingKeyFrames 318
animation réaliste 349
Children 113
Append 64
CircleEase 353
ASUS EP121 252
cisaillement 49
AutoReverse 321
ClipToBounds 134
axes de coordonnées 164
Close 402
Axis 67
collier nul 204
AxisAngleRotation3D 103
Collision 288
axonométriques 75
Color 124
B
ColorAnimation 318
ColorAnimationUsingKeyFrames 318
CompositionTarget 273
BackEase 352 CompositionTarget.Rendering 274
BackMaterial 117 cône 188
Balance 402 cône circulaire droit 189
Begin 320 ConstantAttenuation 128
BeginTime 321 ContainerUIElement3D 116
BitsPerPixel 256 Content 406
BooleanAnimationUsingKeyFrames 318 coordonnées euclidiennes 42
BounceEase 352 coordonnées homogènes 52
Bounds 256 coordonnée sphérique 172
Brush 123, 124 Copy always 406
BufferingEnded 401 Copy to output directory 406
BufferingProgress 401 Count 321
BufferingStarted 401 cube 150
422 Index
cylindre 172 ElasticEase 354
cylindre circulaire 173 EmissiveMaterial 124
Encoding.ASCII 392
D ExponentialEase 355
DecimalAnimation 318
DecimalAnimationUsingKeyFrames 318
F
DependencyProperty 214 facette cylindrique 175
DependencyProperty.Register 214 facette rectangulaire 144
Descartes 25 facette triangulaire 6, 134
Determinant 57 FielOfView 130
DeviceName 256 FillBehavior 322
DiffuseMaterial 122, 124 Forever 321
direction 26 format Wavefront OBJ 387
Direction 128 forme polygonale 185
direction du haut 78 FrameworkElement 400
direction du regard 78
DirectionLight 127
DispatcherTimer 408
DoubleAnimation 318
G
Geometry 117
DoubleAnimationUsingKeyFrames 318
GeometryModel3D 6, 116, 117
DoubleAnimationUsingPath 318
H
DownloadProgress 401
DrawingBrush 125
Duration 321
HasAudio 400
E HasInverse 57
HasVideo 400
HitTestResult 224
EaseIn 350
HoldEnd 322
EaseInOut 350
I
EaseOut 350
EasingFunctionBase 350
éclairage de la scène 6, 127
effet miroir 44 IEasingFunction 349
Elapsed 276 ImageBrush 145
ElapsedMilliseconds 276 InnerConeAngle 128
Index 423
Int16Animation 318 m21 53
Int32Animation 318 m22 53
Int64Animation 318 m23 53
IntAnimationUsingKeyFrames 318 m24 53
IntersectWith 285 m31 53
Interval 408 m32 53
Inverse 91, 99 m33 53
inversible 60 m34 53
Invert 63 Manual 401
IsAffine 57, 91 Material 117, 123
IsBuffering 401 MaterialGroup 124, 125
isEmpty 285 matrice de transformation 43
IsIdentity 57, 67 matrice inversée 60
IsMuted 402 Matrix 104
IsNormalized 67 Matrix3D 57
isométrique 75 Matrix3D.Identity 57
ISO MPEG-1 Layer III 400 Matrix3D.Multiply 63
MatrixAnimationUsingKeyFrames 318
L MatrixAnimationUsingPath 318
MatrixCamera 81, 129
MatrixTransform3D 6, 90, 104
Length 27
MAYA 386
LengthSquared 27, 35
MediaElement 400
Light 127
MediaEnded 402
LinearAttenuation 128
MediaFailed 402
LoadedBehavior 401, 402
MediaOpened 401, 402
Location 285
MediaState 402
longueur 26
MediaState.Close 402
Look Direction 78
MediaState.Manual 402
LookDirection 130
MediaState.Pause 402
M
MediaState.Play 402
MediaState.Stop 402
MeshGeometry3D 118
m11 53 Model3D 114
m12 53 Model3DGroup 116, 117, 214
m13 53 modélisation basique 6, 134
m14 53 ModelUIElement3D 112
424 Index
ModelVisual3D 6, 112, 114 Play 402
MouseWheel 235 Point3DAnimation 318
MP3 400 Point3DAnimationUsingKeyFrames 318
Multiply 34 Point3DCollection 23
Point4D 5, 56
N Point4D.Add 56
Point4D.Equals 56
Point4D.Multiply 56
NaturalDuration 402
Point4D.Parse 56
NaturalVideoHeight 400
Point4D.Substract 56
NaturalVideoWidth 400
PointAnimation 318
Normalize 36
PointAnimationUsingKeyFrames 318
Normals 119
PointAnimationUsingPath 319
O
point de fuite 73
point de fuite axial 73
PointLight 127
OBJ 385 PointLightBase 127
ObjectAnimationUsingKeyFrames 318 Position 402
obliques 75 Positions 118
Offset 56, 285 Prepend 64
OffsetX 57 Primary 256
OffsetY 57 projecteurs 72
OffsetZ 57 projection 5, 72
OnUpdateModel 214 ProjectionCamera 129
OrthographicCamera 130 ProjectionMatrix 81
orthonormale 75 projection parallèle 5, 75, 85
OuterConeAngle 128 projection perspective 5, 73
propagation 212
P PropertyMetadata 215
parallèle 73
Pause 320, 402
Q
perspective 73 QuadraticAttenuation 128
PerspectiveCamera 130 Quaternion 67, 233, 244
placage de texture 156 QuaternionAnimation 318
plan de projection 78 QuaternionAnimationUsingKeyFrames
plan de travail 164 318
Index 425
Quaternion.Identity 67 ScalePrepend 65
QuaternionRotation3D 104, 233, 244 ScaleTransform3D 90, 92
QuinticEase 356, 357, 358 ScaleX 91
ScaleY 91
R ScaleZ 91
Screen 255
Screen.AllScreens 256
Range 128
Screen.PrimaryScreen 256
RayHitTestResult 226
sens 26
RayMeshGeometry3DHitTestResult 224
SetTargetName 320
readonly 214
SetTargetProperty 320
Rect3D 285
SineEase 350
RectAnimation 318
SingleAnimation 318
RectAnimationUsingKeyFrames 318
SingleAnimationUsingKeyFrames 318
réflexion 44
SizeAnimation 318
réflexion spéculaire 6, 126
SizeAnimationUsingKeyFrames 318
Rendering 274
SizeX 285
rendu 72
SizeY 285
rendu d’animation 273
SizeZ 285
RepeatBehavior 321
Source 400, 402
Reset 277
SpecularMaterial 124
Resume 320
SpecularPower 126
Rotate 63
SpeedRatio 321
RotateAt 63
sphère 195
RotatePrepend 69
SpotLight 128
RotateTransform3D 90, 99, 233
Start 276, 408
Rotation 99
StartNew 276
Rotation3DAnimation 318
Stop 320, 402, 408
Rotation3DAnimationUsingKeyFrames
Stopwatch 276
318
Storyboard 320
routage 212
Storyboard.TargetName 320
S
Storyboard.TargetProperty 320
StreamReader 392
StringAnimationUsingKeyFrames 318
scalaire 34 Substract 31
Scale 63 surface conique 188
ScaleAt 63 surface cylindrique 173
426 Index
surface lumineuse 6, 125
surface mate 6, 124
System.Diagnostics 276
V
Value 91
System.Windows.Forms 255
Vector3DAnimation 318
System.Windows.Media.Media3D 285
Vector3DAnimationUsingKeyFrames 318
T
VectorAnimation 318
VectorAnimationUsingKeyFrames 318
ViewMatrix 81
test d’atteinte 221 Viewport3D 112, 227
TextureCoordinates 119 Visual 308
ThicknessAnimation 318 Visual3D 113, 214
ThicknessAnimationUsingKeyFrames 318 Visual3DModel 214, 397
Tick 408 VisualBrush 141, 145, 306
Timeline 321 VisualTreeHelper 224
TimeSpan 277, 321 Volume 402
tore 204 volume d’observation 77
Trackball 227
Transform 63, 81
Transform3D 90
Transform3DCollection 90
W
W 83
Transform3DGroup 90
WAV 400
transformations 3D 90
Wavefront OBJ 385
transformations géométriques 42
Wavefront Technologies 387
TransformGroup3D 109
Width 130
Translate 63
Windows Media Audio 400
TranslatePrepend 64
WMA 400
TranslateTransform3D 90, 96
WorkingArea 256
TriangleIndices 118
X
tunneling 212
U X 18
Y
UIElement3D 6, 116, 212
UnloadedBehavior 402
Up Direction 78
UpDirection 130 Y 18
Index 427
Z
Z 18
colonne colonne colonne colonne colonne colonne colonne colonne
1 2 3 4 5 6 7 8
ligne 1 V q 9 O i N v x
ligne 2 5 A U Z s 4 P h
ligne 3 T p 4 E g u M 8
ligne 4 b F 1 6 Y D y w
ligne 5 G r X B 0 t e Q
ligne 6 f S c 2 n L j z
ligne 7 7 H k I 3 9 C a
ligne 8 W 1 d J m K 5 R