Bienvenue dans ce cinquième article consacré aux axes virtuels basés sur les rotations. Dans le quatrième article, nous avons appris à gérer la modification des facteurs d'échelles et la correction des dimensions qui en résultent. Je vous propose aujourd'hui de terminer notre découverte sur la rotation Z avec des modèles dont la rotation de base est située au centre et sur le côté droit.
Introduction
Comme d'habitude concernant les fonctions EEP, vous pouvez toujours vous référez à la documentation présente sur le site concernant l'utilisation des différentes fonctions Lua pour EEP. Vous pouvez également consulter cette page afin d'apporter des réponses à vos questions et les explications nécessaires aux fonctions et mots-clés propres au langage Lua.
Dans les deuxième et troisième article, nous avons travaillé avec notre "mur-porte" dont sa rotation de base était située sur le côté gauche. Nous allons maintenant choisir un modèle avec une rotation d'origine de base située au centre dont le quatrième article a présenté les fondamentaux afin d'ajuster les dimensions correctes pour mener à bien les opérations.
Présentation du modèle
Voici en image le modèle utilisé pour notre premier exemple :
Et en vidéo avec la rotation de base uniquement avec la fonction EEPStructureSetRotation() et une boucle numérique :
L'animation nous montre clairement une rotation d'origine située au centre. Le repère rouge et blanc marque le point de rotation.
Nous allons commencer notre exercice avec une translation du point de rotation d'origine situé au centre vers la gauche ce qui aura pour effet d'ouvrir le panneau à partir du côté gauche.
Déplacer le point de rotation du centre vers la gauche
Avant de rentrer dans le vif du sujet, je ne vais pas revenir en intégralité sur l’interaction entre les fonctions, les variables, etc... Nous allons plutôt nous concentrer sur les nouvelles fonctions et les nouveaux paramètres pour mener à bien notre travail. Voici en vidéo ce que nous désirons obtenir :
Nous allons prendre comme exemple, le modèle ID16 pour nous accompagner pendant la durée de l'exercice.
Comme d'habitude, les noms des fonctions sont renseignés dans les propriétés des contacts et sont situées dans le fichier Projet_principal.lua aux lignes n° 471 et 477 :
-- Ouverture du mur ID16
function fn_Contact_Open_ID16()
Mod.Ouvre_ID16("#16")
end
-- Fermeture du mur ID16
function fn_Contact_Close_ID16()
Mod.Ferme_ID16("#16")
end
A la ligne n° 473 est appelée la fonction Mod.Ouvre_ID16("#16") située dans le fichier ID_Modeles.lua :
-- Ouverture du mur ID16
function M.Ouvre_ID16(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBoisSG1 pour récupérer les paramètres communs du mur composite
M.ImmoBoisSG1(Ar_ID_Lua)
-- Incrémentation de 0.2 degré à chaque itération de la boucle
gTb_Rot_Increment[Ar_ID_Lua] = 0.2
-- Valeur maximale à atteindre en degrés pour le mur ID16
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_AXE_Z, C.VERS_LARRIERE, C.ROT_Z_MILIEU_GAUCHE, C.OUVERTURE)
end
A la ligne n° 688, la fonction M.ImmoBoisSG1(Ar_ID_Lua) est appelée et va renseigner les tables pour les paramètres communs du modèle :
Fonction M.ImmoBoisSG1(Ar_ID_Lua)
-- ===============================================================================
-- = =
-- = Modèle : Mur insonorisé (mur en bois et composite) du constructeur SG1 =
-- = M.ImmoBoisSG1 concerne les modèles avec les ID : =
-- = 1, 16, 17, 18, 74, 75, 76 =
-- = =
-- ===============================================================================
-- Paramètres communs pour le mur en bois et composite
function M.ImmoBoisSG1(Ar_ID_Lua)
-- La longueur du mur Ar_ID_Lua à l'échelle 1 est = à 20 mètres
gTb_Long_X_Ech1[Ar_ID_Lua] = 20
-- La largeur du mur Ar_ID_Lua à l'échelle 1 est = à 0.30 mètre
gTb_Larg_Y_Ech1[Ar_ID_Lua] = 0.24
-- La hauteur du mur Ar_ID_Lua à l'échelle 1 est = à 4 mètres
gTb_Haut_Z_Ech1[Ar_ID_Lua] = 4
--[[ Variable pour stocker la valeur calculée des rotations
à chaque itération de la boucle while ]]
gTb_Rot_Calcul[Ar_ID_Lua] = 0
gTb_Rot_Origine[Ar_ID_Lua] = C.ROTATION_ORIGINE_CENTRE
end
Ensuite aux lignes n° 690 et 692, le programme récupère respectivement les valeurs des tables gTb_Rot_Increment[Ar_ID_Lua] et gTb_Rot_Max[Ar_ID_Lua].
Pour terminer à la ligne n° 694, la fonction fn_Operation(.....) est appelée avec les arguments suivants :
fn_Operation(Ar_ID_Lua, C.ROTATION_AXE_Z, C.VERS_LARRIERE, C.ROT_Z_MILIEU_GAUCHE, C.OUVERTURE)
- Le premier argument correspond au n° ID du modèle,
- Le deuxième argument implique une rotation Z,
- Le troisième argument demande une ouverture vers l'arrière,
- Le quatrième argument implique une rotation sur le côté gauche et au milieu.
- Le cinquième argument active l'opération pour l'ouverture.
Reprenons le début de notre fonction fn_Ouverture(.....) avec la déclaration des variables et table locales, etc...
function fn_Operation(Ar_ID_Lua, Ar_Rot_Souhait, Ar_Sens_Mouvement, Ar_Pos_Pt_Rot, Ar_Operation)
-- /** Variables et tables locales **/
-- Table pour recevoir les valeurs calculées des positions X, Y et Z à chaque itération de la boucle while
local Tb_Calcul_Pos = {}
-- Variables pour recevoir les valeurs cibles X, Y et Z calculées selon le type de rotation demandée
local Nb_Pos_X_Cible, Nb_Pos_Y_Cible, Nb_Pos_Z_Cible = 0, 0, 0
-- Variable pour stocker la fonction anonyme correspondante au type de rotation demandée
local Rotation_Locale = nil
-- Variable pour recevoir la valeur maximale ou minimale selon l'ouverture ou la fermeture
local Nb_Rotation_Min_Max = 0
Je vais afficher la synthèse des arguments passés à la fonction fn_Operation(.....) qui nous servira de support pour la suite et sera également disponible sous forme d'infobulle contextuelle dans le but de faciliter la lecture plus bas dans l'article. Je vais également ajouter quelques paramètres communs du modèle utilisé ainsi nous aurons toutes les informations sous la main et nous évitera de jongler entre les différentes fonctions :
Nous sommes dans l'opération pour l'ouverture, le test conditionnel à la ligne n° 1291 est vérifié et validé :
-- Les calculs s'effectuent uniquement pendant l'opération de l'ouverture
if Ar_Operation == C.OUVERTURE then
-- Récupèrer les valeurs des positions de la structure n° Ar_ID_Lua (format "#xx)
Bool_Ok, gTb_Pos_X[Ar_ID_Lua], gTb_Pos_Y[Ar_ID_Lua], gTb_Pos_Z[Ar_ID_Lua] = EEPStructureGetPosition( Ar_ID_Lua )
-- Récupèrer les valeurs des rotations de la structure n° Ar_ID_Lua (format "#xx)
Bool_Ok, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Z[Ar_ID_Lua] = EEPStructureGetRotation( Ar_ID_Lua )
-- Récupère la longueur en mètres sur l'axe X du modèle après application éventuelle de la nouvelle échelle
gTb_Ech_Dimension_X[Ar_ID_Lua] = fn_EEPStructureGetScale( Ar_ID_Lua, C.ROTATION_AXE_X, gTb_Long_X_Ech1[Ar_ID_Lua] )
-- Récupère la largeur en mètres sur l'axe Y du modèle après application éventuelle de la nouvelle échelle
gTb_Ech_Dimension_Y[Ar_ID_Lua] = fn_EEPStructureGetScale( Ar_ID_Lua, C.ROTATION_AXE_Y, gTb_Larg_Y_Ech1[Ar_ID_Lua] )
-- Récupère la hauteur en mètres sur l'axe Z du modèle après application éventuelle de la nouvelle échelle
gTb_Ech_Dimension_Z[Ar_ID_Lua] = fn_EEPStructureGetScale( Ar_ID_Lua, C.ROTATION_AXE_Z, gTb_Haut_Z_Ech1[Ar_ID_Lua] )
print("\ngTb_Ech_Dimension_X : ", gTb_Ech_Dimension_X[Ar_ID_Lua])
print("gTb_Ech_Dimension_Y : ", gTb_Ech_Dimension_Y[Ar_ID_Lua])
print("gTb_Ech_Dimension_Z : ", gTb_Ech_Dimension_Z[Ar_ID_Lua], "\n")
if gTb_Rot_Origine[Ar_ID_Lua] == C.ROTATION_ORIGINE_CENTRE then
gTb_Corr_Pos_X[Ar_ID_Lua] = ( gTb_Long_X_Ech1[Ar_ID_Lua] / 2 ) + ( ( gTb_Ech_Dimension_X[Ar_ID_Lua] - gTb_Long_X_Ech1[Ar_ID_Lua] ) / 2 )
--print( "gTb_Corr_Pos_X[Ar_ID_Lua] C.ROTATION_ORIGINE_CENTRE : ", gTb_Corr_Pos_X[Ar_ID_Lua] )
gTb_Corr_Pos_Y[Ar_ID_Lua] = ( gTb_Larg_Y_Ech1[Ar_ID_Lua] / 2 ) + ( ( gTb_Ech_Dimension_Y[Ar_ID_Lua] - gTb_Larg_Y_Ech1[Ar_ID_Lua] ) / 2 )
--print( "gTb_Corr_Pos_Y[Ar_ID_Lua] C.ROTATION_ORIGINE_CENTRE : ", gTb_Corr_Pos_Y[Ar_ID_Lua] )
La partie intéressante commence à partir de la ligne n° 1311 et concerne le test conditionnel. Il nous indique que nous sommes bien sur une rotation d'origine au centre car la valeur de la table gTb_Rot_Origine[Ar_ID_Lua] est bien égale à C.ROTATION_ORIGINE_CENTRE. Comme nous l'avons étudié dans l'article précédent, les lignes n° 1313 et 1316 se chargent des calculs pour ajuster les bonnes dimensions. Ensuite le programme continue directement à la ligne n° 1590 car nous sommes sur une rotation Z. Toutes les parties dédiées pour les rotations X ou Y après la ligne n° 1316 jusqu'à la ligne n° 1590 sont ignorées :
-- /** Sinon si la rotation souhaitée concerne la rotation Z **/
elseif Ar_Rot_Souhait == C.ROTATION_AXE_Z then
Analyse du test conditionnel - 1ère partie
Arrivée à ce stade, notre fonction fn_Operation(.....) a déjà intégré notre demande pour une rotation Z. C'est bien ! mais elle ne sait pas encore quel traitement précis appliquer car il lui manque les tests des autres arguments concernant les positions X et Y par exemple. L'axe de position Z ne bouge pas en rotation Z ce qui est logique vu que que la rotation s'effectue autour de l'axe du même nom. Ainsi pour sélectionner les bons calculs plusieurs conditions doivent être testées et nous allons commencer par l'axe de position Y.
Voici le premier groupe des tests conditionnels pour renseigner les variables locales Nb_Pos_Y_Cible et Rotation_Locale :
Comme dans le troisième article, deux fonctions locales sont implémentées et seront utilisées en fonction du résultat des tests :
- fn_WhileInferieurOuEgal_Z() : cette fonction gère la boucle avec la condition inférieure ou égale ( <= ),
- fn_WhileSuperieurOuEgal_Z() : cette fonction gère la boucle avec la condition supérieure ou égale ( >= ).
Fonctions fn_WhileInferieurOuEgal_Z() et fn_WhileSuperieurOuEgal_Z()
local function fn_WhileInferieurOuEgal_Z()
-- Tant que la valeur calculée de la rotation est inférieure ou égale à Nb_Rotation_Min_Max alors la boucle continue...
while gTb_Rot_Calcul[Ar_ID_Lua] <= Nb_Rotation_Min_Max do
-- gTb_Rot_Calcul[Ar_ID_Lua] est incrémentée (valeur exprimée en degré)
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] + gTb_Rot_Increment[Ar_ID_Lua]
-- Appliquer la rotation Z
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua] )
-- Calculer dynamiquement les positions X et Y en fonction de la progression du calcul de l'angle pour la rotation Z
Tb_Calcul_Pos = Rotation_Locale( { gTb_Pos_X[Ar_ID_Lua], gTb_Pos_Y[Ar_ID_Lua] }, { Nb_Pos_X_Cible, Nb_Pos_Y_Cible }, gTb_Rot_Calcul[Ar_ID_Lua] )
-- Appliquer les valeurs calculées pour les positions X et Y
EEPStructureSetPosition( Ar_ID_Lua, Tb_Calcul_Pos[1], Tb_Calcul_Pos[2], gTb_Pos_Z[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
end
local function fn_WhileSuperieurOuEgal_Z()
-- Tant que la valeur calculée de la rotation est supérieure ou égale à Nb_Rotation_Min_Max alors la boucle continue...
while gTb_Rot_Calcul[Ar_ID_Lua] >= Nb_Rotation_Min_Max do
-- gTb_Rot_Calcul[Ar_ID_Lua] est décrémentée (valeur exprimée en degré)
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] - gTb_Rot_Increment[Ar_ID_Lua]
-- Appliquer la rotation Z
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua] )
-- Calculer dynamiquement les positions X et Y en fonction de la progression du calcul de l'angle pour la rotation Z
Tb_Calcul_Pos = Rotation_Locale( { gTb_Pos_X[Ar_ID_Lua], gTb_Pos_Y[Ar_ID_Lua] }, { Nb_Pos_X_Cible, Nb_Pos_Y_Cible}, gTb_Rot_Calcul[Ar_ID_Lua] )
-- Appliquer les valeurs calculées pour les positions X et Y
EEPStructureSetPosition( Ar_ID_Lua, Tb_Calcul_Pos[1], Tb_Calcul_Pos[2], gTb_Pos_Z[Ar_ID_Lua] )
-- Temporisation
fn_Tempo(C.TEMPO)
end
end
Dans tous les cas selon le test validé, la variable Nb_Pos_Y_Cible se verra attribuer la valeur correcte pour la position Y et la variable Rotation_Locale héritera la référence de la fonction de rotation adéquate. Nous reviendrons plus tard sur le choix des valeurs et référence assignées aux deux variables.
Ci-dessous, la partie du code avec les trois tests. Toutes les lignes concernées dans notre exemple sont mises en surbrillance :
La partie du code avec les trois tests
-- Toutes les rotations demandées au milieu, des côtés gauche ou droit
if Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_DROIT then
-- La position Y cible est par défaut la position Y dans les propriétés du modèle
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua]
-- Passer la fonction anonyme fn_Rotation_Z dans la variable Rotation_Locale
Rotation_Locale = fn_Rotation_Z
elseif Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_DROIT then
-- Si la rotation demandée est décalée vers le coin arrière gauche ou droit du modèle,
-- additionner la position Y dans les propriétés du modèle et la correction Y
-- La correction Y est égale à la largeur de l'axe Y à l'échelle 1 et
-- prend en compte la modification éventuelle de l'échelle Y
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua] + gTb_Corr_Pos_Y[Ar_ID_Lua]
-- Passer la fonction anonyme fn_Rotation_Coin dans la variable Rotation_Locale
Rotation_Locale = fn_Rotation_Coin
elseif Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_DROIT then
-- Si la rotation demandée est décalée vers le coin avant gauche ou droit du modèle,
-- soustraire de la position Y dans les propriétés du modèle la correction Y
-- La correction Y est égale à la largeur de l'axe Y à l'échelle 1 et
-- prend en compte la modification éventuelle de l'échelle Y
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua] - gTb_Corr_Pos_Y[Ar_ID_Lua]
-- Passer la fonction anonyme fn_Rotation_Coin dans la variable Rotation_Locale
Rotation_Locale = fn_Rotation_Coin
end
Dans le cas de notre modèle, il suffit de regarder dans la synthèse ID16 pour connaitre la valeur du 4ème argument Ar_Pos_Pt_Rot. Dans notre exemple il s'agit de la constante C.ROT_Z_MILIEU_GAUCHE. Ainsi le point de rotation est fixé sur le côté gauche et au milieu de celui-ci. Si on se réfère à la partie du code avec les trois tests, celui de la ligne n° 1625 est vérifié et la condition est vraie. Le programme renseigne les deux variables Nb_Pos_Y_Cible, Rotation_Locale, ignore les deux autres tests et passe directement après la ligne n° 1652.
Nous venons de franchir la première étape car la fonction vient d'intégrer ces nouveaux éléments. Un petit récapitulatif rapide :
- Nous sommes sur une rotation Z,
- La variable Nb_Pos_Y_Cible est paramétrée avec la valeur correcte pour la position Y. Dans notre exemple Nb_Pos_Y_Cible est égal à gTb_Pos_Y[Ar_ID_Lua] à la ligne n° 1628,
- La variable Rotation_Locale a hérité la référence de la fonction de rotation adéquate. Dans notre exemple Rotation_Locale est égal à fn_Rotation_Z à la ligne n° 1630.
Il nous reste maintenant à déterminer la valeur correcte pour la position X, la valeur négative ou positive pour l'angle maximale de l'ouverture et appeler la fonction correspondante pour exécuter la boucle.
Analyse du test conditionnel - 2ème partie
La deuxième partie se décompose en deux. Dans le troisième article nous avions découvert le premier volet (partie n° 2a) concernant le côté droit. Regardons maintenant le deuxième volet concernant le côté gauche (partie n° 2b) afin de renseigner les variables locales Nb_Pos_X_Cible, Nb_Rotation_Min_Max et déterminer la fonction fn_WhileInferieurOuEgal_Z() ou fn_WhileSuperieurOuEgal_Z() afin d'exécuter la boucle de l'ouverture :
Ci-dessous, voici le code correspondant à partir de la ligne 1707. Toutes les lignes concernées dans notre exemple sont mises en surbrillance :
Extrait de la fonction fn_Operation(....)
elseif ( Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_GAUCHE ) then
-- Le calcul ci-dessous concernent les trois origines :
-- C.ROTATION_ORIGINE_GAUCHE et C.ROTATION_ORIGINE_CENTRE et C.ROTATION_ORIGINE_DROITE
-- Un test conditionnel n'est pas nécessaire pour tester ces trois valeurs afin
-- de déterminer le point de translation car gTb_Corr_Pos_X[Ar_ID_Lua] est utilisée pour cela.
-- Si la rotation est demandée sur le côté gauche du modèle, quelque soit les trois rotations
-- d'origine du modèle, soustraire la position X - la correction X
Nb_Pos_X_Cible = gTb_Pos_X[Ar_ID_Lua] - gTb_Corr_Pos_X[Ar_ID_Lua]
if Ar_Sens_Mouvement == C.VERS_LARRIERE then
if Ar_Operation == C.OUVERTURE then
-- L'ouverture de la porte en ouverture avant en rotation Z à partir du côté gauche pour les trois rotations
-- d'origine impose une valeur positive obtenue avec math.abs et la valeur par défaut enregistrée dans la table gTb_Rot_Max[Ar_ID_Lua]
Nb_Rotation_Min_Max = math.abs( gTb_Rot_Max[Ar_ID_Lua] )
-- Appelle la boucle while avec la condition supérieure ou égale
fn_WhileInferieurOuEgal_Z()
elseif Ar_Operation == C.FERMETURE then
Nb_Rotation_Min_Max = gTb_Rot_Min[Ar_ID_Lua]
-- Appelle la boucle while avec la condition inférieure ou égale
fn_WhileSuperieurOuEgal_Z()
end
elseif Ar_Sens_Mouvement == C.VERS_LAVANT then
if Ar_Operation == C.OUVERTURE then
-- L'ouverture de la porte en ouverture arrière en rotation Z à partir du côté gauche pour les trois rotations
-- d'origine impose la valeur négative par défaut enregistrée dans la table gTb_Rot_Max[Ar_ID_Lua]
Nb_Rotation_Min_Max = gTb_Rot_Max[Ar_ID_Lua]
-- Appelle la boucle while avec la condition supérieure ou égale
fn_WhileSuperieurOuEgal_Z()
elseif Ar_Operation == C.FERMETURE then
Nb_Rotation_Min_Max = gTb_Rot_Min[Ar_ID_Lua]
-- Appelle la boucle while avec la condition inférieure ou égale
fn_WhileInferieurOuEgal_Z()
end
end
Le premier test débute à la ligne n° 1707. Il est étalé sur 3 lignes pour une question de lisibilité. L'opérateur conditionnel utilisé est or qui se traduit par ou.
Explication : si Ar_Pos_Pt_Rot est égal à C.ROT_Z_COIN_AVANT_GAUCHE ou Ar_Pos_Pt_Rot est égal à C.ROT_Z_MILIEU_GAUCHE ou Ar_Pos_Pt_Rot est égal à C.ROT_Z_COIN_ARRIERE_GAUCHE alors... le programme continue à la ligne n° 1718 pour tester l'origine de base de la rotation. Pour le savoir, il suffit de regarder dans la synthèse ID16.
Dans l'exemple de notre modèle, la constante C.ROTATION_ORIGINE_CENTRE nous informe qu'il s'agit d'une rotation d'origine de base située au centre du modèle.
La ligne n° 1718 se charge de renseigner la variable Nb_Pos_X_Cible.
Ensuite le programme passe directement à la ligne n° 1720. Ce test concerne le type de mouvement demandé, soit vers l'arrière ou soit vers l'avant. Dans les deux cas, cette information est disponible dans le 3ème argument Ar_Sens_Mouvement comme nous pouvons le vérifier dans la synthèse ID101 avec la constante C.VERS_LARRIERE. Le test est ainsi validé et la condition est vraie.
Arrivé à ce stade il ne reste plus qu'à déterminer le type d'opération demandée, c'est-à-dire l'ouverture ou la fermeture de notre modèle. Cette information est disponible dans le 5ème argument Ar_Operation comme nous pouvons aussi le vérifier dans la synthèse ID16. Ici est concernée l'opération pour l'ouverture. A la ligne n° 1722 le test étant validé, les lignes n° 1726 et 1728 vont être exécutées.
La ligne n° 1726 se charge de renseigner la variable Nb_Rotation_Min_Max avec la valeur de la table gTb_Rot_Max[Ar_ID_Lua] située dans la fonction Mod.Ouvre_ID101("#101") indiquée dans la synthèse ID16 et fixée à -88°. Ici la valeur sera positive avec l'utilisation de la fonction Lua math.abs().
Et pour terminer la ligne n° 1728 appelle la fonction fn_WhileInferieurOuEgal_Z() pour démarrer le processus d'ouverture de notre modèle.
Fonction locale fn_WhileInferieurOuEgal_Z()
Rappelez vous : les deux fonctions locales fn_WhileInferieurOuEgal_Z() et fn_WhileSuperieurOuEgal_Z() concernent les boucles pour les ouvertures mais aussi pour les fermetures. Un des avantages vient du fait même de leurs emplacements particuliers. Vu qu'elles sont imbriquées dans la fonction fn_Operation(.....), les variables et tables locales à la fonction fn_Operation(.....) deviennent... globales pour ces deux fonctions et il est inutile de passer les valeurs des variables et tables sous forme d'arguments dans les en-têtes des deux fonctions.
Je vais maintenant détailler la fonction fn_WhileInferieurOuEgal_Z(). Nous verrons pour l'opération de la fermeture sa jumelle fn_WhileSuperieurOuEgal_Z().
Tout d'abord, voici le code de cette fonction :
Fonction fn_WhileInferieurOuEgal_Z()
local function fn_WhileInferieurOuEgal_Z()
-- Tant que la valeur calculée de la rotation est inférieure ou égale à Nb_Rotation_Min_Max alors la boucle continue...
while gTb_Rot_Calcul[Ar_ID_Lua] <= Nb_Rotation_Min_Max do
-- gTb_Rot_Calcul[Ar_ID_Lua] est incrémentée (valeur exprimée en degré)
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] + gTb_Rot_Increment[Ar_ID_Lua]
-- Appliquer la rotation Z
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua] )
-- Calculer dynamiquement les positions X et Y en fonction de la progression du calcul de l'angle pour la rotation Z
Tb_Calcul_Pos = Rotation_Locale( { gTb_Pos_X[Ar_ID_Lua], gTb_Pos_Y[Ar_ID_Lua] }, { Nb_Pos_X_Cible, Nb_Pos_Y_Cible }, gTb_Rot_Calcul[Ar_ID_Lua] )
-- Appliquer les valeurs calculées pour les positions X et Y
EEPStructureSetPosition( Ar_ID_Lua, Tb_Calcul_Pos[1], Tb_Calcul_Pos[2], gTb_Pos_Z[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
end
Cette fonction gère aussi bien l'ouverture ou la fermeture avec une boucle conditionnelle et l'opérateur relationnel inférieur ou égal ( <= ) d'où son nom.
Cette boucle démarre à la ligne n° 1587. La condition de notre boucle while est la suivante : Tant que gTb_Rot_Calcul[Ar_ID_Lua] est inférieur ou égal à Nb_Rotation_Min_Max, ce qui se traduit par : tant que 0 est inférieur ou égal à 88°, la boucle continue encore et encore.
Si nous regardons dans la synthèse ID16, la valeur contenue dans la table gTb_Rot_Calcul[Ar_ID_Lua] va être mise à jour après l'addition (à la ligne n° 1590) de la valeur gTb_Rot_Increment[Ar_ID_Lua] qui est égale à 0.2 degré.
Une fois l'opération effectuée, la ligne n° 1592 applique tout simplement la rotation Z classique.
Comme nous l'avons déjà vu dans le troisième article, la variable Rotation_Locale avait hérité de la fonction fn_Rotation_Z à la ligne n° 1630 :
L'héritage de la fonction fn_Rotation_Z
-- Toutes les rotations demandées au milieu, des côtés gauche ou droit
if Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_DROIT then
-- La position Y cible est par défaut la position Y dans les propriétés du modèle
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua]
-- Passer la fonction anonyme fn_Rotation_Z dans la variable Rotation_Locale
Rotation_Locale = fn_Rotation_Z
elseif Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_DROIT then
-- Si la rotation demandée est décalée vers le coin arrière gauche ou droit du modèle,
-- additionner la position Y dans les propriétés du modèle et la correction Y
-- La correction Y est égale à la largeur de l'axe Y à l'échelle 1 et
-- prend en compte la modification éventuelle de l'échelle Y
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua] + gTb_Corr_Pos_Y[Ar_ID_Lua]
-- Passer la fonction anonyme fn_Rotation_Coin dans la variable Rotation_Locale
Rotation_Locale = fn_Rotation_Coin
elseif Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_DROIT then
-- Si la rotation demandée est décalée vers le coin avant gauche ou droit du modèle,
-- soustraire de la position Y dans les propriétés du modèle la correction Y
-- La correction Y est égale à la largeur de l'axe Y à l'échelle 1 et
-- prend en compte la modification éventuelle de l'échelle Y
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua] - gTb_Corr_Pos_Y[Ar_ID_Lua]
-- Passer la fonction anonyme fn_Rotation_Coin dans la variable Rotation_Locale
Rotation_Locale = fn_Rotation_Coin
end
Ce mécanisme d'héritage est très intéressant car il permet dans une seule et même variable d'assigner selon le cas une fonction différente par rapport au contexte. Dans notre cas, il s'agit de la fonction fn_Rotation_Z. Mais selon les critères demandés comme par exemple une rotation au coin arrière gauche, la variable Rotation_Locale hériterait d'une autre fonction intitulée fn_Rotation_Coin (aux lignes n° 1640 ou 1650). La variable Rotation_Locale devient à cet instant une fonction anonyme dans le jargon informatique.
Les calculs de la fonction anonyme sont retournés dans la table Tb_Calcul_Pos déclarée au début de la fonction fn_Operation(.....). Comme nous l'avons déjà vu dans le troisième article, le fait de recourir à l'usage d'une table nous apporte déjà un élément de réponse important. Il est fort probable que la fonction Rotation_Locale retourne au minimum deux valeurs mais lesquelles ?
Comme d'habitude, l'axe de position Z ne bouge pas si vous manipulez la rotation Z ce qui est logique vu que la rotation s'effectue autour de l'axe du même nom. Ainsi par déduction il nous reste les axes de positions X et Y ! En effet, ces deux axes vont être calculés à chaque itération de la boucle et c'est la modification des positions X et Y combinée à la rotation Z qui va nous permettre d'ouvrir vers l'arrière notre modèle à partir du côté droit au milieu même si de base, la rotation d'origine ne peut s'effectuer qu'à partir du centre.
Nous allons découvrir les trois arguments passés à la fonction mais avant, je vais afficher un petit dessin avec les coordonnées X, Y du point de rotation souhaité :
Rappel de la ligne 1594 avec la fonction Rotation_Locale :
Tb_Calcul_Pos = Rotation_Locale( { gTb_Pos_X[Ar_ID_Lua], gTb_Pos_Y[Ar_ID_Lua] }, { Nb_Pos_X_Cible, Nb_Pos_Y_Cible }, gTb_Rot_Calcul[Ar_ID_Lua] )
Dans un soucis de simplicité, cette fonction comprend trois arguments et peut être décomposée en trois parties :
1ère partie
Argument { gTb_Pos_X[Ar_ID_Lua], gTb_Pos_Y[Ar_ID_Lua] }. Il s'agit des axes de positions d'origine X et Y du modèle enregistrées dans les tables gTb_Pos_X et gTb_Pos_Y. Dans notre exemple X = 250 m et Y = 200 m. Ces deux valeurs sont entourées par des accolades et envoyées à la fonction formatées en tant que table.
2ème partie
Argument { Nb_Pos_X_Cible, Nb_Pos_Y_Cible }. Ces deux valeurs ont été calculées dans les tests plus haut. Rappel du calcul de la variable Nb_Pos_X_Cible à la ligne n° 1718 :
Extrait de la fonction fn_Operation(....)
elseif ( Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_GAUCHE ) then
-- Le calcul ci-dessous concernent les trois origines :
-- C.ROTATION_ORIGINE_GAUCHE et C.ROTATION_ORIGINE_CENTRE et C.ROTATION_ORIGINE_DROITE
-- Un test conditionnel n'est pas nécessaire pour tester ces trois valeurs afin
-- de déterminer le point de translation car gTb_Corr_Pos_X[Ar_ID_Lua] est utilisée pour cela.
-- Si la rotation est demandée sur le côté gauche du modèle, quelque soit les trois rotations
-- d'origine du modèle, soustraire la position X - la correction X
Nb_Pos_X_Cible = gTb_Pos_X[Ar_ID_Lua] - gTb_Corr_Pos_X[Ar_ID_Lua]
Cette variable est le résultat de la position X initiale gTb_Pos_X égale à 250 m auquel on soustrait la valeur de la table gTb_Corr_Pos_X égale à 10. Cette valeur a été calculée à la ligne n° 1313 :
if gTb_Rot_Origine[Ar_ID_Lua] == C.ROTATION_ORIGINE_CENTRE then
gTb_Corr_Pos_X[Ar_ID_Lua] = ( gTb_Long_X_Ech1[Ar_ID_Lua] / 2 ) + ( ( gTb_Ech_Dimension_X[Ar_ID_Lua] - gTb_Long_X_Ech1[Ar_ID_Lua] ) / 2 )
Comme nous l'avons vu dans le quatrième article, ce calcul est le résultat de la longueur du modèle à l'échelle 1 divisé par deux et c'est le rôle de la première partie du calcul. Dans notre exemple, il n'y a pas de modification d'échelle et la seconde partie du calcul est égale à 0. Notre modèle fait 20 mètres de longueur à l'échelle 1 et 20 / 2 = 10 mètres. Au final la valeur dans la table gTb_Pos_X[Ar_ID_Lua] est égale à 250 m - 10 m = 240 mètres.
Le fait de faire "glisser" un point de rotation vers un autre s'appelle un mouvement de translation. Passons maintenant à la variable Nb_Pos_Y_Cible. Si nous regardons bien le dessin, la position Y est exactement la même au centre et sur le côté gauche. Le calcul à la ligne n° 1628 nous le prouve :
Extrait de la fonction fn_Operation(....)
-- Toutes les rotations demandées au milieu, des côtés gauche ou droit
if Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_DROIT then
-- La position Y cible est par défaut la position Y dans les propriétés du modèle
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua]
Rien de plus simple : comme la position Y ne bouge pas, Nb_Pos_Y_Cible est égal à la valeur gTb_Pos_Y qui correspond à la position Y initiale.
Ces deux valeurs sont entourées par des accolades et envoyées à la fonction formatées en tant que table.
3ème partie
Argument gTb_Rot_Calcul[Ar_ID_Lua]. Il s'agit tout naturellement du calcul de l'angle à la ligne n° 1590 dans la boucle située à l'intérieur de la fonction fn_WhileInferieurOuEgal_Z().
Passons maintenant à la fonction fn_Rotation_Z !
Fonction trigonométrique fn_Rotation_Z
Même si nous avons déjà analysé cette fonction dans le troisième article, il n'est pas inutile de l'afficher une nouvelle fois :
-- **********************************************************************************
-- * *
-- * Cette fonction retourne les positions X et Y dans le cas d'une rotation Z *
-- * avec un point de rotation souhaité au milieu de l'axe Y *
-- * *
-- * M = table avec 2 arguments (Voir l'appel de la fonction pour les arguments) *
-- * O = table avec 2 arguments (Voir l'appel de la fonction pour les arguments) *
-- * Angle = angle de rotation Z recalculé à chaque itération de la boucle while *
-- * *
-- **********************************************************************************
fn_Rotation_Z = function(Ar_M, Ar_O, Ar_Angle)
local x, y, x1, y1
Ar_Angle = Ar_Angle * C.PI180
-- Translation du point pour que le point de rotation soit placé à la position voulue
x1 = Ar_M[1] - Ar_O[1]
y1 = Ar_M[2] - Ar_O[2]
-- Appliquer la rotation
x = x1 * math.cos( Ar_Angle ) - y1 * math.sin( Ar_Angle ) + Ar_O[1]
y = x1 * math.sin( Ar_Angle ) - y1 * math.cos( Ar_Angle ) + Ar_O[2]
-- Retourne les positions X et Y
return { x, y }
end
Reportez-vous au paragraphe du troisième article si vous souhaitez plus de détails.
La fonction est maintenant terminée, le programme retourne dans la fonction fn_WhileInferieurOuEgal_Z() à la ligne n° 1596 :
Fonction fn_WhileInferieurOuEgal_Z()
local function fn_WhileInferieurOuEgal_Z()
-- Tant que la valeur calculée de la rotation est inférieure ou égale à Nb_Rotation_Min_Max alors la boucle continue...
while gTb_Rot_Calcul[Ar_ID_Lua] <= Nb_Rotation_Min_Max do
-- gTb_Rot_Calcul[Ar_ID_Lua] est incrémentée (valeur exprimée en degré)
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] + gTb_Rot_Increment[Ar_ID_Lua]
-- Appliquer la rotation Z
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua] )
-- Calculer dynamiquement les positions X et Y en fonction de la progression du calcul de l'angle pour la rotation Z
Tb_Calcul_Pos = Rotation_Locale( { gTb_Pos_X[Ar_ID_Lua], gTb_Pos_Y[Ar_ID_Lua] }, { Nb_Pos_X_Cible, Nb_Pos_Y_Cible }, gTb_Rot_Calcul[Ar_ID_Lua] )
-- Appliquer les valeurs calculées pour les positions X et Y
EEPStructureSetPosition( Ar_ID_Lua, Tb_Calcul_Pos[1], Tb_Calcul_Pos[2], gTb_Pos_Z[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
end
Tout ce qu'il nous reste à faire est d'appliquer la nouvelle position au modèle avec la fonction EEPStructureSetPosition() :
- Le premier argument correspond au n° ID du modèle répertorié dans l'argument Ar_ID_Lua,
- Le deuxième argument se rapporte au premier indice de la table Tb_Calcul_Pos[1] et représente la nouvelle position X,
- Le troisième argument se rapporte au deuxième indice de la table Tb_Calcul_Pos[2] et représente la nouvelle position Y,
- Et le quatrième argument correspond à la position Z enregistrée dans la table gTb_PosZ[Ar_ID_Lua] qui reste inchangée car l'axe de position Z ne bouge pas si vous manipulez la rotation Z.
Ainsi les nouvelles positions sont recalculées autant de fois que la valeur de l'angle est modifiée à chaque itération de la boucle.
A la ligne n° 1598, la fonction fn_Tempo(C_.TEMPO) entre en action et la boucle recommence tant que sa condition est vérifiée.
Une fois celle-ci achevée, la fonction fn_Operation(.....) se termine, les variables et table locales sont détruites et le processus d'ouverture est terminé.
Nous allons maintenant passer à l'opération pour la fermeture !
La fonction fn_Operation() et l'opération pour la fermeture
Le nouveau paramètre à découvrir
L'opération pour la fermeture ressemble beaucoup à celle de l'ouverture car nous allons faire appel à la même fonction fn_Operation(.....). Néanmoins, il y a deux changements à prendre en considération :
Le première changement se situe au niveau du test conditionnel à la ligne n° 1291 :
Fonction fn_Operation()
-- Les calculs s'effectuent uniquement pendant l'opération de l'ouverture
if Ar_Operation == C.OUVERTURE then
-- Récupèrer les valeurs des positions de la structure n° Ar_ID_Lua (format "#xx)
Bool_Ok, gTb_Pos_X[Ar_ID_Lua], gTb_Pos_Y[Ar_ID_Lua], gTb_Pos_Z[Ar_ID_Lua] = EEPStructureGetPosition( Ar_ID_Lua )
-- Récupèrer les valeurs des rotations de la structure n° Ar_ID_Lua (format "#xx)
Bool_Ok, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Z[Ar_ID_Lua] = EEPStructureGetRotation( Ar_ID_Lua )
-- Récupère la longueur en mètres sur l'axe X du modèle après application éventuelle de la nouvelle échelle
gTb_Ech_Dimension_X[Ar_ID_Lua] = fn_EEPStructureGetScale( Ar_ID_Lua, C.ROTATION_AXE_X, gTb_Long_X_Ech1[Ar_ID_Lua] )
-- Récupère la largeur en mètres sur l'axe Y du modèle après application éventuelle de la nouvelle échelle
gTb_Ech_Dimension_Y[Ar_ID_Lua] = fn_EEPStructureGetScale( Ar_ID_Lua, C.ROTATION_AXE_Y, gTb_Larg_Y_Ech1[Ar_ID_Lua] )
-- Récupère la hauteur en mètres sur l'axe Z du modèle après application éventuelle de la nouvelle échelle
gTb_Ech_Dimension_Z[Ar_ID_Lua] = fn_EEPStructureGetScale( Ar_ID_Lua, C.ROTATION_AXE_Z, gTb_Haut_Z_Ech1[Ar_ID_Lua] )
print("\ngTb_Ech_Dimension_X : ", gTb_Ech_Dimension_X[Ar_ID_Lua])
print("gTb_Ech_Dimension_Y : ", gTb_Ech_Dimension_Y[Ar_ID_Lua])
print("gTb_Ech_Dimension_Z : ", gTb_Ech_Dimension_Z[Ar_ID_Lua], "\n")
if gTb_Rot_Origine[Ar_ID_Lua] == C.ROTATION_ORIGINE_CENTRE then
gTb_Corr_Pos_X[Ar_ID_Lua] = ( gTb_Long_X_Ech1[Ar_ID_Lua] / 2 ) + ( ( gTb_Ech_Dimension_X[Ar_ID_Lua] - gTb_Long_X_Ech1[Ar_ID_Lua] ) / 2 )
gTb_Corr_Pos_Y[Ar_ID_Lua] = ( gTb_Larg_Y_Ech1[Ar_ID_Lua] / 2 ) + ( ( gTb_Ech_Dimension_Y[Ar_ID_Lua] - gTb_Larg_Y_Ech1[Ar_ID_Lua] ) / 2 )
elseif gTb_Rot_Origine[Ar_ID_Lua] == C.ROTATION_ORIGINE_GAUCHE then
if Ar_Pos_Pt_Rot == C.ROT_Y_A_DROITE or
Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_DROIT or
Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_DROIT or
Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_DROIT then
gTb_Corr_Pos_X[Ar_ID_Lua] = gTb_Ech_Dimension_X[Ar_ID_Lua]
else
-- Par déduction, ici Ar_Pos_Pt_Rot = toutes les positions de rotations vers la gauche
gTb_Corr_Pos_X[Ar_ID_Lua] = 0
end
gTb_Corr_Pos_Y[Ar_ID_Lua] = gTb_Ech_Dimension_Y[Ar_ID_Lua] / 2
elseif gTb_Rot_Origine[Ar_ID_Lua] == C.ROTATION_ORIGINE_DROITE then
if Ar_Pos_Pt_Rot == C.ROT_Y_A_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_GAUCHE then
gTb_Corr_Pos_X[Ar_ID_Lua] = gTb_Ech_Dimension_X[Ar_ID_Lua]
else
-- Par déduction, ici Ar_Pos_Pt_Rot = toutes les positions de rotations vers la droite
gTb_Corr_Pos_X[Ar_ID_Lua] = 0
end
gTb_Corr_Pos_Y[Ar_ID_Lua] = gTb_Ech_Dimension_Y[Ar_ID_Lua] / 2
end
-- gTb_Corr_Pos_Z[Ar_ID_Lua] ne concerne que la rotation Y à partir du bord supérieur gauche ou droit
gTb_Corr_Pos_Z[Ar_ID_Lua] = gTb_Ech_Dimension_Z[Ar_ID_Lua]
end -- Fin du test pour l'argument si l'opération demandée concerne l'ouverture
Lorsque la fonction fn_Operation(.....) est appelée pour l'opération de la fermeture, les valeurs des positions et rotations déjà enregistrées lors de l'ouverture dans les tables globales n'ont pas besoin d'être à nouveau récupérées. Ceci est aussi valable pour les calculs des éventuelles modifications d'échelles. Le bloc complet du code situé entre les lignes n° 1290 et 1358 n'est pas exécuté pour l'opération de la fermeture.
Sinon le deuxième et dernier changement concerne la seule information manquante qui correspond à la valeur angulaire minimale pour la fermeture, comme nous l'avions déjà vu dans l'article précédent pour la rotation de base.
Je ne vais pas revenir sur l'analyse de la première partie de l'ordinogramme qui est strictement identique à l'opération consacrée à l'ouverture. Avant d'aller plus loin, nous avons juste besoin de connaitre la valeur minimale de l'angle pour la fermeture. Cette information est consignée dans la fonction M.Ferme_ID16(Ar_ID_Lua) à la ligne n° 697 :
Fonction M.Ferme_ID16(Ar_ID_Lua)
-- Fermeture du mur ID16
function M.Ferme_ID16(Ar_ID_Lua)
-- Valeur minimale à atteindre en degrés pour le mur ID16
gTb_Rot_Min[Ar_ID_Lua] = 0
fn_Operation(Ar_ID_Lua, C.ROTATION_AXE_Z, C.VERS_LARRIERE, C.ROT_Z_MILIEU_GAUCHE, C.FERMETURE)
end
La table gTb_Rot_Min[Ar_ID_Lua] contient la valeur minimal de l'angle fixée ici à 0°. Naturellement nous retrouvons exactement les mêmes arguments à la ligne n° 703 comme dans l'opération pour l'ouverture excepté le cinquième argument avec la constante C.FERMETURE.
Reprenons la deuxième partie de notre ordinogramme. Comme il s'agit du même modèle avec les mêmes paramètres, nous pouvons passer directement aux tests pour l'opération de la fermeture.
Reprenons l'extrait de la fonction fn_Operation(.....) et regardons les tests correspondants. Toutes les lignes concernées dans notre exemple sont mises en surbrillance :
Extrait de la fonction fn_Operation(....)
elseif ( Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_GAUCHE ) then
-- Le calcul ci-dessous concernent les trois origines :
-- C.ROTATION_ORIGINE_GAUCHE et C.ROTATION_ORIGINE_CENTRE et C.ROTATION_ORIGINE_DROITE
-- Un test conditionnel n'est pas nécessaire pour tester ces trois valeurs afin
-- de déterminer le point de translation car gTb_Corr_Pos_X[Ar_ID_Lua] est utilisée pour cela.
-- Si la rotation est demandée sur le côté gauche du modèle, quelque soit les trois rotations
-- d'origine du modèle, soustraire la position X - la correction X
Nb_Pos_X_Cible = gTb_Pos_X[Ar_ID_Lua] - gTb_Corr_Pos_X[Ar_ID_Lua]
if Ar_Sens_Mouvement == C.VERS_LARRIERE then
if Ar_Operation == C.OUVERTURE then
-- L'ouverture de la porte en ouverture avant en rotation Z à partir du côté gauche pour les trois rotations
-- d'origine impose une valeur positive obtenue avec math.abs et la valeur par défaut enregistrée dans la table gTb_Rot_Max[Ar_ID_Lua]
Nb_Rotation_Min_Max = math.abs( gTb_Rot_Max[Ar_ID_Lua] )
-- Appelle la boucle while avec la condition supérieure ou égale
fn_WhileInferieurOuEgal_Z()
elseif Ar_Operation == C.FERMETURE then
Nb_Rotation_Min_Max = gTb_Rot_Min[Ar_ID_Lua]
-- Appelle la boucle while avec la condition inférieure ou égale
fn_WhileSuperieurOuEgal_Z()
end
elseif Ar_Sens_Mouvement == C.VERS_LAVANT then
if Ar_Operation == C.OUVERTURE then
-- L'ouverture de la porte en ouverture arrière en rotation Z à partir du côté gauche pour les trois rotations
-- d'origine impose la valeur négative par défaut enregistrée dans la table gTb_Rot_Max[Ar_ID_Lua]
Nb_Rotation_Min_Max = gTb_Rot_Max[Ar_ID_Lua]
-- Appelle la boucle while avec la condition supérieure ou égale
fn_WhileSuperieurOuEgal_Z()
elseif Ar_Operation == C.FERMETURE then
Nb_Rotation_Min_Max = gTb_Rot_Min[Ar_ID_Lua]
-- Appelle la boucle while avec la condition inférieure ou égale
fn_WhileInferieurOuEgal_Z()
end
end
end
if Ar_Operation == C.FERMETURE then
-- Bug lors de la dernière opération si gTb_Rot_Calcul[Ar_ID_Lua] = 0
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Min[Ar_ID_Lua] )
-- Faire correspondre dans la table gTb_Rot_Calcul la valeur contenue dans gTb_Rot_Min[Ar_ID_Lua] (synchronisation)
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Min[Ar_ID_Lua]
end
Comme le cinquième argument de la fonction fn_Operation(.....) correspond à la constante C.FERMETURE, la seule différence par rapport à l'ouverture se situe à partir de la ligne n° 1730. Le test va être validé car la condition est vérifiée et vraie. A la ligne n° 1732, la valeur angulaire minimale va être transférer dans la variable Nb_Rotation_Min_Max. A la ligne n° 1734, il ne reste plus qu'à appeler la fonction locale fn_WhileSuperieurOuEgal_Z().
Fonction locale fn_WhileSuperieurOuEgal_Z()
Comme je l'ai déjà expliqué à l'ouverture, cette fonction est locale à la fonction fn_Operation(.....). Ainsi les tables et variables utilisées dans fn_Operation(.....) sont accessibles dans fn_WhileSuperieurOuEgal_Z().
Voici le code de cette fonction :
Fonction fn_WhileSuperieurOuEgal_Z()
local function fn_WhileSuperieurOuEgal_Z()
-- Tant que la valeur calculée de la rotation est supérieure ou égale à Nb_Rotation_Min_Max alors la boucle continue...
while gTb_Rot_Calcul[Ar_ID_Lua] >= Nb_Rotation_Min_Max do
-- gTb_Rot_Calcul[Ar_ID_Lua] est décrémentée (valeur exprimée en degré)
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] - gTb_Rot_Increment[Ar_ID_Lua]
-- Appliquer la rotation Z
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua] )
-- Calculer dynamiquement les positions X et Y en fonction de la progression du calcul de l'angle pour la rotation Z
Tb_Calcul_Pos = Rotation_Locale( { gTb_Pos_X[Ar_ID_Lua], gTb_Pos_Y[Ar_ID_Lua] }, { Nb_Pos_X_Cible, Nb_Pos_Y_Cible}, gTb_Rot_Calcul[Ar_ID_Lua] )
-- Appliquer les valeurs calculées pour les positions X et Y
EEPStructureSetPosition( Ar_ID_Lua, Tb_Calcul_Pos[1], Tb_Calcul_Pos[2], gTb_Pos_Z[Ar_ID_Lua] )
-- Temporisation
fn_Tempo(C.TEMPO)
end
end
Celle-ci gère aussi bien l'ouverture ou la fermeture selon le cas avec une boucle conditionnelle et l'opérateur relationnel supérieur ou égal ( >= ) d'où son nom.
Cette boucle démarre à la ligne n° 1607. La condition de notre boucle while est la suivante : Tant que gTb_Rot_Calcul[Ar_ID_Lua] est supérieur ou égal à Nb_Rotation_Min_Max, ce qui se traduit par : tant que 88 est supérieur ou égal à 0°, la boucle continue encore et encore.
Rappelez-vous à l'ouverture, la valeur calculée dans la table gTb_Rot_Calcul[Ar_ID_Lua] était égale à la valeur angulaire maximale fixée à -88 mais nous avions utilisé la fonction Lua math.abs() pour convertir -88 en valeur positive = à 88. Deux avantages justifient l'utilisation d'une table globale pour conserver cette valeur :
- Cette valeur reste accessible à n'importe quel endroit du script,
- Inutile d'utiliser la fonction EEPStructureGetRotation() pour récupérer à nouveau la valeur angulaire Z si celle-ci n'avait pas été enregistrée pendant l'opération de l'ouverture.
Ainsi la valeur contenue dans la table gTb_Rot_Calcul[Ar_ID_Lua] va être mise à jour après la soustraction (à la ligne n° 1610) de la valeur gTb_Rot_Increment[Ar_ID_Lua] qui est égale à 0.2 degré.
Une fois l'opération effectuée, la ligne n° 1612 applique tout simplement la rotation Z classique.
Ensuite comme à l'ouverture, nous sommes toujours sur une rotation Z au milieu du coté droit. La variable Rotation_Locale avait hérité de la fonction fn_Rotation_Z à la ligne n° 1630 :
L'héritage de la fonction fn_Rotation_Z
-- Toutes les rotations demandées au milieu, des côtés gauche ou droit
if Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_DROIT then
-- La position Y cible est par défaut la position Y dans les propriétés du modèle
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua]
-- Passer la fonction anonyme fn_Rotation_Z dans la variable Rotation_Locale
Rotation_Locale = fn_Rotation_Z
elseif Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_DROIT then
-- Si la rotation demandée est décalée vers le coin arrière gauche ou droit du modèle,
-- additionner la position Y dans les propriétés du modèle et la correction Y
-- La correction Y est égale à la largeur de l'axe Y à l'échelle 1 et
-- prend en compte la modification éventuelle de l'échelle Y
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua] + gTb_Corr_Pos_Y[Ar_ID_Lua]
-- Passer la fonction anonyme fn_Rotation_Coin dans la variable Rotation_Locale
Rotation_Locale = fn_Rotation_Coin
elseif Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_GAUCHE or Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_DROIT then
-- Si la rotation demandée est décalée vers le coin avant gauche ou droit du modèle,
-- soustraire de la position Y dans les propriétés du modèle la correction Y
-- La correction Y est égale à la largeur de l'axe Y à l'échelle 1 et
-- prend en compte la modification éventuelle de l'échelle Y
Nb_Pos_Y_Cible = gTb_Pos_Y[Ar_ID_Lua] - gTb_Corr_Pos_Y[Ar_ID_Lua]
-- Passer la fonction anonyme fn_Rotation_Coin dans la variable Rotation_Locale
Rotation_Locale = fn_Rotation_Coin
end
A la ligne n° 1614, les calculs retournés par la fonction anonyme Rotation_Locale sont transférés dans la table Tb_Calcul_Pos.
Tout ce qu'il nous reste à faire à la ligne n° 1616 est d'appliquer la nouvelle position au modèle avec la fonction EEPStructureSetPosition() :
- Le premier argument correspond au n° ID du modèle répertorié dans l'argument Ar_ID_Lua,
- Le deuxième argument se rapporte au premier indice de la table Tb_Calcul_Pos[1] et représente la nouvelle position X,
- Le troisième argument se rapporte au deuxième indice de la table Tb_Calcul_Pos[2] et représente la nouvelle position Y,
- Et le quatrième argument correspond à la position Z enregistrée dans la table gTb_PosZ[Ar_ID_Lua] qui reste inchangée car l'axe de position Z ne bouge pas si vous manipulez la rotation Z.
Ainsi les nouvelles positions sont recalculées autant de fois que la valeur de l'angle est modifiée à chaque itération de la boucle.
A la ligne n° 1618, la fonction fn_Tempo(C_.TEMPO) entre en action et la boucle recommence tant que sa condition est vérifiée.
Une fois celle-ci achevée, la fonction fn_Operation(.....) se termine, les variables et table locales sont détruites et le processus d'ouverture est terminé.
Maintenant, notre "porte-panneau" devrait être revenu à sa position initiale ! Nous allons le vérifier en vidéo :
Le piquet rouge et blanc au centre du modèle symbolise la rotation d'origine au centre.
Ce qu'il faut retenir : L' utilisation du cercle trigonométrique permet de "déplacer" un point de rotation vers une position ( X, Y ) quelconque sur un plan. On appelle cela un mouvement de translation. Comme le point de rotation n'est plus celui d'origine (du centre, il est passé à gauche), les positions doivent être recalculées pour "maintenir" le modèle à sa place tout en appliquant ladite rotation.
Dans cet exemple, nous avons choisi le milieu du côté gauche mais nous pouvons positionner librement le point de rotation où bon nous semble. Il suffit pour cela de connaitre les nouvelles coordonnées X et Y du nouveau point. Par exemple, par s'amuser nous pouvons très bien déplacer le point de rotation du milieu à gauche vers le milieu du côté droit !
Ci-dessous une petite vidéo pour vous montrer que c'est tout à fait possible :
Pour parvenir à ce résultat, il faut juste modifier deux constantes dans les fonctions M.Ouvre_ID16(Ar_ID_Lua) et M.Ferme_ID16(Ar_ID_Lua) comme ceci :
Fonction M.Ouvre_ID16(Ar_ID_Lua)
-- Ouverture du mur ID16
function M.Ouvre_ID16(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBoisSG1 pour récupérer les paramètres communs du mur composite
M.ImmoBoisSG1(Ar_ID_Lua)
-- Incrémentation de 0.2 degré à chaque itération de la boucle
gTb_Rot_Increment[Ar_ID_Lua] = 0.2
-- Valeur maximale à atteindre en degrés pour le mur ID16
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_AXE_Z, C.VERS_LARRIERE, C.ROT_Z_MILIEU_DROIT, C.OUVERTURE)
end
Aux lignes n° 694 et 703, il suffit de remplacer le quatrième argument C.ROT_Z_MILIEU_GAUCHE par C.ROT_Z_MILIEU_DROIT et le tour est joué !
Fonction M.Ferme_ID16(Ar_ID_Lua)
-- Fermeture du mur ID16
function M.Ferme_ID16(Ar_ID_Lua)
-- Valeur minimale à atteindre en degrés pour le mur ID16
gTb_Rot_Min[Ar_ID_Lua] = 0
fn_Operation(Ar_ID_Lua, C.ROTATION_AXE_Z, C.VERS_LARRIERE, C.ROT_Z_MILIEU_DROIT, C.FERMETURE)
end
On peut remarquer ici l'avantage d'utiliser des constantes avec des noms explicites afin d'améliorer significativement la compréhension au lieu d'utiliser directement des nombres dans les différents tests des fonctions.
Jusqu'à présent nous avons vu l'ouverture vers l'arrière. Et bien sur la base des exercices précédents, nous allons terminer la rotation Z avec un exemple concernant l'ouverture vers l'avant !... Comment ça ? ce n'est pas possible ? mais bien sûr que si ! 😉
Ouverture vers l'avant en rotation Z
Dans cet exercice, nous allons conserver notre "mur-panneau" mais cette fois-ci avec l'ouverture vers l'avant à partir du coté gauche.
L'ouverture vers l'avant n'est pas plus compliqué que l'ouverture vers l'arrière. Logiquement, avec l'ouverture en avant, tous les calculs sont inversés par rapport à l'ouverture en arrière. Je ne vous apprends rien mais il fallait quand même le dire !
Voici en vidéo ce que nous désirons obtenir :
Bon ok, esthétiquement ce n'est pas top ! mais pour une petite démonstration, je pense que cela suffira.
Nous allons prendre comme exemple, le modèle ID140 pour nous accompagner pendant la durée de l'exercice.
Comme d'habitude, les noms des fonctions sont renseignés dans les propriétés des contacts et celles-ci sont situées dans le fichier Projet_principal.lua aux lignes n° 426 et 432 :
-- Ouverture du mur ID140
function fn_Contact_Open_ID140()
Mod.Ouvre_ID140("#140")
end
-- Fermeture du mur ID140
function fn_Contact_Close_ID140()
Mod.Ferme_ID140("#140")
end
A la ligne n° 428 est appelée la fonction Mod.Ouvre_ID140("#140") située dans le fichier ID_Modeles.lua :
-- Ouverture du mur ID140
function M.Ouvre_ID140(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBoisSG1 pour récupérer les paramètres communs du mur composite
M.ImmoBoisSG1(Ar_ID_Lua)
-- Incrémentation de 0.2 degré à chaque itération de la boucle
gTb_Rot_Increment[Ar_ID_Lua] = 0.2
-- Valeur maximale à atteindre en degrés pour le mur ID140
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_AXE_Z, C.VERS_LAVANT, C.ROT_Z_MILIEU_GAUCHE, C.OUVERTURE)
end
A la ligne n° 605, la fonction M.ImmoBoisSG1(Ar_ID_Lua) est appelée et va renseigner les tables pour les paramètres communs du modèle :
Fonction M.ImmoBoisSG1(Ar_ID_Lua)
-- ===============================================================================
-- = =
-- = Modèle : Mur insonorisé (mur en bois et composite) du constructeur SG1 =
-- = M.ImmoBoisSG1 concerne les modèles avec les ID : =
-- = 1, 16, 17, 18, 74, 75, 76 =
-- = =
-- ===============================================================================
-- Paramètres communs pour le mur en bois et composite
function M.ImmoBoisSG1(Ar_ID_Lua)
-- La longueur du mur Ar_ID_Lua à l'échelle 1 est = à 20 mètres
gTb_Long_X_Ech1[Ar_ID_Lua] = 20
-- La largeur du mur Ar_ID_Lua à l'échelle 1 est = à 0.30 mètre
gTb_Larg_Y_Ech1[Ar_ID_Lua] = 0.24
-- La hauteur du mur Ar_ID_Lua à l'échelle 1 est = à 4 mètres
gTb_Haut_Z_Ech1[Ar_ID_Lua] = 4
--[[ Variable pour stocker la valeur calculée des rotations
à chaque itération de la boucle while ]]
gTb_Rot_Calcul[Ar_ID_Lua] = 0
gTb_Rot_Origine[Ar_ID_Lua] = C.ROTATION_ORIGINE_CENTRE
end
Ensuite aux lignes n° 607 et 609, le programme récupère respectivement les valeurs des tables gTb_Rot_Increment[Ar_ID_Lua] et gTb_Rot_Max[Ar_ID_Lua].
Pour terminer à la ligne n° 611, la fonction fn_Operation(.....) est appelée avec les arguments suivants :
fn_Operation(Ar_ID_Lua, C.ROTATION_AXE_Z, C.VERS_LAVANT, C.ROT_Z_MILIEU_GAUCHE, C.OUVERTURE)
- Le premier argument correspond au n° ID du modèle,
- Le deuxième argument implique une rotation Z,
- Le troisième argument demande une ouverture vers l'avant,
- Le quatrième argument implique une rotation sur le côté gauche et au milieu.
- Le cinquième argument active l'opération pour l'ouverture.
Je ne vais pas revenir sur la déclaration des variables, le code pour les calculs, etc... mais je vais plutôt afficher la synthèse des arguments passés à la fonction fn_Operation(.....) qui nous servira de support pour la suite et sera également disponible sous forme d'infobulle contextuelle dans le but de faciliter la lecture plus bas dans l'article. Je vais également ajouter quelques paramètres communs du modèle utilisé ainsi nous aurons toutes les informations sous la main et nous évitera de jongler entre les différentes fonctions :
Comme le cheminement est identique à l'ouverture en arrière, nous allons reprendre la deuxième partie de l'ordinogramme :
Ci-dessous, voici le code correspondant à partir de la ligne 1707. Toutes les lignes concernées dans notre exemple sont mises en surbrillance :
Extrait de la fonction fn_Operation(....)
elseif ( Ar_Pos_Pt_Rot == C.ROT_Z_COIN_AVANT_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_MILIEU_GAUCHE or
Ar_Pos_Pt_Rot == C.ROT_Z_COIN_ARRIERE_GAUCHE ) then
-- Le calcul ci-dessous concernent les trois origines :
-- C.ROTATION_ORIGINE_GAUCHE et C.ROTATION_ORIGINE_CENTRE et C.ROTATION_ORIGINE_DROITE
-- Un test conditionnel n'est pas nécessaire pour tester ces trois valeurs afin
-- de déterminer le point de translation car gTb_Corr_Pos_X[Ar_ID_Lua] est utilisée pour cela.
-- Si la rotation est demandée sur le côté gauche du modèle, quelque soit les trois rotations
-- d'origine du modèle, soustraire la position X - la correction X
Nb_Pos_X_Cible = gTb_Pos_X[Ar_ID_Lua] - gTb_Corr_Pos_X[Ar_ID_Lua]
if Ar_Sens_Mouvement == C.VERS_LARRIERE then
if Ar_Operation == C.OUVERTURE then
-- L'ouverture de la porte en ouverture avant en rotation Z à partir du côté gauche pour les trois rotations
-- d'origine impose une valeur positive obtenue avec math.abs et la valeur par défaut enregistrée dans la table gTb_Rot_Max[Ar_ID_Lua]
Nb_Rotation_Min_Max = math.abs( gTb_Rot_Max[Ar_ID_Lua] )
-- Appelle la boucle while avec la condition supérieure ou égale
fn_WhileInferieurOuEgal_Z()
elseif Ar_Operation == C.FERMETURE then
Nb_Rotation_Min_Max = gTb_Rot_Min[Ar_ID_Lua]
-- Appelle la boucle while avec la condition inférieure ou égale
fn_WhileSuperieurOuEgal_Z()
end
elseif Ar_Sens_Mouvement == C.VERS_LAVANT then
if Ar_Operation == C.OUVERTURE then
-- L'ouverture de la porte en ouverture arrière en rotation Z à partir du côté gauche pour les trois rotations
-- d'origine impose la valeur négative par défaut enregistrée dans la table gTb_Rot_Max[Ar_ID_Lua]
Nb_Rotation_Min_Max = gTb_Rot_Max[Ar_ID_Lua]
-- Appelle la boucle while avec la condition supérieure ou égale
fn_WhileSuperieurOuEgal_Z()
elseif Ar_Operation == C.FERMETURE then
Nb_Rotation_Min_Max = gTb_Rot_Min[Ar_ID_Lua]
-- Appelle la boucle while avec la condition inférieure ou égale
fn_WhileInferieurOuEgal_Z()
end
end
Comme d'habitude, à la ligne n° 1744 la variable Nb_Rotation_Min_Max récupère la valeur angulaire maximale pour l'ouverture qui est ici = à -88° comme nous l'indique la synthèse ID140.
La ligne n° 1746 appelle la fonction fn_WhileSuperieurOuEgal_Z() comme nous l'avons vu dans le précédent exercice pour l'ouverture vers l'arrière.
Une fois le panneau ouvert, la fonction fn_Contact_Close_ID140() est appelée via le contact pour démarrer l'opération de la fermeture qui est strictement identique à l'exercice précédent. A la ligne n° 1750 la variable Nb_Rotation_Min_Max récupère la valeur angulaire minimale pour l'ouverture qui est ici = 0° comme nous l'indique la synthèse ID140.
Pour terminer la ligne n° 1752 appelle la fonction fn_WhileInferieurOuEgal_Z() comme nous l'avons déjà vu dans l'exercice précédent.
Les opérations d'ouverture et de fermeture étant strictement les mêmes aussi bien en avant qu'en arrière, je ne vais pas toutes les détailler mais plutôt faire un résumé global concernant la rotation Z.
Résumé global concernant la rotation Z
Afin de bien clarifier la situation, je vais résumer les éléments étudiés pour mener à bien la rotation Z des objets.
Les différents points de rotation d'origine des modèles sur l'axe X
Il y a (hormis quelques rares exceptions) trois points de rotation principaux concernant l'axe X répertoriés dans les trois constantes suivantes :
- C.ROTATION_ORIGINE_GAUCHE : l'origine de la rotation est située à gauche,
- C.ROTATION_ORIGINE_CENTRE : l'origine de la rotation est située au centre,
- C.ROTATION_ORIGINE_DROITE : l'origine de la rotation est située à droite.
Pour rappel, ces points de rotations sont le résultat par rapport à la construction interne des modèles. Nous avons besoin de connaitre en amont ces points de rotation d'origine pour déterminer le calcul correct à appliquer.
Ci-dessous un petit dessin :
Les différents emplacements possibles pour les rotations sur l'axe Y
Les différents points de rotation sur l'axe Y sont répertoriés dans les six constantes suivantes :
- C.ROT_Z_COIN_AVANT_GAUCHE : le point de rotation est situé au coin avant à gauche,
- C.ROT_Z_MILIEU_GAUCHE : le point de rotation est situé au milieu du côté à gauche,
- C.ROT_Z_COIN_ARRIERE_GAUCHE : le point de rotation est situé au coin arrière à gauche,
- C.ROT_Z_COIN_AVANT_DROIT : le point de rotation est situé au coin avant à droite,
- C.ROT_Z_MILIEU_DROIT : le point de rotation est situé au milieu du côté à droite,
- C.ROT_Z_COIN_ARRIERE_DROIT : le point de rotation est situé au coin arrière à droite.
Voici un petit dessin pour visualiser les différents points de rotation sur l'axe Y :
La rotation de base sans faire appel aux calculs
J'ai également inclus la rotation de base sans faire appel aux calculs uniquement dans le but de repérer et comprendre la rotation d'origine des modèles. Si vous voulez appliquer la rotation de base, il faut utiliser une des trois constantes suivantes dans le quatrième argument lors de l'appel de la fonction fn_Operation(....) :
- C.ROT_X : Appliquer la rotation standard sur l'axe X,
- C.ROT_Y : Appliquer la rotation standard sur l'axe Y,
- C.ROT_Z : Appliquer la rotation standard sur l'axe Z.
et en deuxième argument la constante : C.ROTATION_BASE :
Exemple des arguments pour la rotation de base
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.OUVERTURE)
Vous pouvez utiliser les exemples du projet et vous amuser à modifier les arguments afin de voir les changements en direct.
Conclusion
Dans les trois derniers articles, nous avons appris à manipuler la rotation Z sous toutes ses formes. J'ai conscience qu'il n'est pas aussi facile d'appréhender ce sujet surtout au début avec des modèles qui ne sont absolument pas conçus au départ pour être utilisés de cette façon !
Nous avons enfin terminé de découvrir la rotation Z ! Dans le prochaine article nous allons maintenant lever le voile sur la rotation Y qui contrairement à la rotation Z est beaucoup plus simple à mettre en œuvre ! 🙂
Je vous remercie de m'avoir suivi jusqu'ici et je vous dis à très bientôt !
Merci d'avoir lu cet article. Si vous avez des questions ou des suggestions, n’hésitez pas à nous en faire part en bas de cette page en commentaires.
Amusez-vous à lire un autre article. A bientôt !