Bienvenue dans ce deuxième article consacré aux axes virtuels basés sur les rotations. Dans le premier article, nous avons découvert ensemble les rotations de base des modèles avec les fonctions Lua correspondantes. Nous allons maintenant approfondir le sujet et commencer à découvrir le projet conçu spécialement pour cette série de tutoriels.
Introduction
Dans ce tutoriel, nous allons utiliser le langage Lua disponible dans EEP pour mener à bien nos travaux. Je suppose que vous êtes familiarisés avec ce langage, vous connaissez également le principe de fonctionnement des variables, des fonctions, des boucles, etc.... Vous pouvez également consulter cette page afin d'apporter des réponses à vos interrogations et les explications nécessaires aux fonctions et mots-clés propres au langage Lua. N'oubliez pas non plus la documentation présente sur le site concernant l'utilisation des différentes fonctions Lua pour EEP. Sinon pour les fonctions Lua utilisées dans cet article, je n'oublie pas les débutants car je vous donne les explications nécessaires au moment opportun.
Je vais faire de mon mieux pour vous faciliter la lecture et rester le plus simple possible. Si quelque chose vous échappe ou si vous ne comprenez pas une partie du code, n'hésitez pas à laisser un commentaire en bas de l'article, je pourrai ainsi répondre à vos attentes ou vos interrogations.
Cette série d'articles, basée sur le concept de l'apprentissage vous amènera graduellement au fil du temps vers des sujets un peu plus avancés.
Je pense qu'il n'est pas inutile de mettre en place de l'aide contextuelle lorsque vous en aurez besoin, sur des mots clés du langage Lua ou encore le nom d'une variable ou d'un élément qui nécessiterait une précision. Lorsque vous verrez un mot avec des pointillés bleu comme par exemple le nom de cette variable var_nbr_pos, passez le curseur de la souris sur le mot pour faire apparaitre une infobulle afin d'afficher une description en rapport avec le mot souligné.
De même lorsque vous voyez un bandeau de couleur, cliquez dessus pour l'ouvrir ou le fermer. En général, vous y trouverez des extraits de fonctions comme piqûres de rappel pour éviter de faire défiler la page inutilement :
Cliquez une fois pour ouvrir et re-cliquez pour refermer
Ce deuxième article va se limiter sobrement à la rotation Z basique mais en posant graduellement les fondations pour les articles suivants. Ainsi, vous posséderez le socle nécessaire pour aller plus loin avec les possibilités offertes par Lua afin d'apprendre par exemple comment déplacer ou créer "virtuellement" des points de rotation à l'instar des éléments mobiles pour des modèles qui en sont dépourvus à la base. Et à cet instant, vous ne mesurez sans doute pas à quel point on peut allez très loin dans ce domaine !
On y va ? alors c'est parti dans l'univers des rotations !
Présentation du projet de test
J'ai créé un projet de test pour illustrer les différents cas de figure qui peuvent se présenter. Ce projet est assez important, car il aborde les 3 rotations X, Y et Z avec différents modèles dans diverses configurations.
Je pourrais très bien vous demander de télécharger le projet dès maintenant mais vu son importance avec environ 4400 lignes de script Lua (même si le code est abondamment commenté et rédigé avec une écriture "aérée"), je vous propose malgré tout de respecter une courbe d'apprentissage nécessaire pour avancer pas à pas. De cette manière, nous allons assimiler tranquillement la découverte du script. A la fin de l'article, les bases seront intégrées et nous pourrons passer à l'article suivant avec une bonne compréhension du projet.
Ci-dessous, voici quelques images du projet :
Pour chaque type de rotation, nous allons étudier quasiment tous les cas de figure qui peuvent se présenter. La rotation Z est de loin la plus importante par rapport aux autres rotations mais plus importante ne rime pas forcément avec plus difficile ! Nous allons donc commencer par elle car une fois maitrisée, comprendre les rotations X et Y deviendra un jeu d'enfant !
Présentation des différents fichiers lua
Le script de ce projet étant assez volumineux, je l'ai scindé en trois parties :
- Le fichier Projet_principal.lua du projet lui-même (contient toutes les fonctions... principales et l'appel des contacts),
- Un second fichier nommé ID_Modeles.lua (ce fichier contient toutes les fonctions pour les opérations d'ouverture et de fermeture et les paramètres des modèles en action),
- Un troisième fichier nommé Const_Var.lua (ce petit fichier contient toutes les constantes utilisées et partagées aussi bien dans le script principal que dans le fichier secondaire ID_Modeles.lua).
La raison pour laquelle j'ai découpé le script complet en trois parties (hormis sa longueur) est de faciliter la maintenance du code plus aisément avec plusieurs petits fichiers qu'un gros fichier unique.
Voilà pour la partie pratique sinon, au demeurant, EEP "voit" le script en un seul et même fichier. En interne, il assemble les trois pour en faire un seul.
Le script principal
Pour déclarer les fichiers séparés, il suffit juste d'écrire ces quelques lignes de code au début du script principal pour qu'EEP gère lui-même l'assemblage des trois fichiers :
clearlog()
function EEPMain()
return 1
end
-- Constante pour le chemin d'accès au dossier du projet (A ajuster selon le cas)
-- Ne pas oublier de doubler l'anti-slash \\ dans le chemin d'accès sinon une erreur se produira !
C_CHEMIN = "E:\\Trend_FR\\Projets\\Portes\\Rotation_finale\\"
-- Définir le chemin d'accès pour les fichiers lua requis pour ce projet
package.path = C_CHEMIN.."?.lua"
-- Récupère dans la variable globale Mod les fonctions incluses des modèles dans le fichier ID_Modeles.lua
Mod = require("ID_Modeles")
-- Récupère dans la variable globale C les constantes incluses dans le fichier Const_Var.lua
C = require("Const_Var")
Lorsque le script est exécuté, EEP va fusionner le contenu des deux fichiers et l'ajouter au script principal... et puis c'est tout. Vous n'avez pas à vous préoccuper du reste. Naturellement, la logique voudrait que tous les fichiers soient situés dans le même dossier. L'idéal étant bien sûr le dossier du projet EEP.
Le fichier ID_Modeles.lua
Ce fichier contient toutes les fonctions pour les opérations d'ouverture et de fermeture ainsi que les paramètres des modèles en action.
Ci-dessous, trois fonctions prises au hasard dans ce fichier :
-- Ouverture du pont ID90
function M.Ouvre_ID90(Ar_ID_Lua)
-- Appelle la fonction M.ImmoPontSM2 pour récupérer les paramètres communs du pont
M.ImmoPontSM2(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 ID90
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_AXE_Y, C.ROT_Y_BORD_SUP, C.ROT_Y_A_GAUCHE, C.OUVERTURE)
end
-- Fermeture du mur ID90
function M.Ferme_ID90(Ar_ID_Lua)
-- Valeur minimale à atteindre en degrés pour le mur ID90
gTb_Rot_Min[Ar_ID_Lua] = 0
fn_Operation(Ar_ID_Lua, C.ROTATION_AXE_Y, C.ROT_Y_BORD_SUP, C.ROT_Y_A_GAUCHE, C.FERMETURE)
end
-- Paramètres communs pour le pont
function M.ImmoPontSM2(Ar_ID_Lua)
-- La longueur du pont à l'échelle 1 est = à 6 mètres
gTb_Long_X_Ech1[Ar_ID_Lua] = 6
-- La largeur du pont à l'échelle 1 est = à 1.8 mètre
gTb_Larg_Y_Ech1[Ar_ID_Lua] = 1.8
-- La hauteur du pont à l'échelle 1 est = à 0.75 mètres
gTb_Haut_Z_Ech1[Ar_ID_Lua] = 0.75
--[[ 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
Ici c'est très simple : à la ligne n° 1414, commence la fonction M.Ouvre_ID90(ID_Lua). La ligne n° 1417 appelle et récupère tous les paramètres communs du pont via la fonction M.ImmoPontSM2(ID_Lua) située à partir de la ligne n° 2056. En effet, il est inutile d'écrire dans toutes les fonctions M.Ouvre_IDxx(ID_xx) les paramètres du modèle car plusieurs fonctions l'utilise avec différentes ouvertures et rotations. Il serait stupide d'écrire X fois les mêmes paramètres communs. Il suffit de les rédiger une bonne fois pour toute dans une fonction unique et de l'appeler au gré des besoins du script.
Ensuite l'exécution du programme reprend sous la ligne 1417. Nous aurons l'occasion de revenir sur le reste du code plus bas dans cet article.
Le fichier Const_Var.lua
Ce petit fichier contient toutes les constantes utilisées aussi bien dans le script principal que dans le fichier secondaire ID_Modeles.lua. Voici le code dans son intégralité :
local C = {}
-- Constante pour les calculs trigonométriques
C.PI180 = math.pi / 180
-- Constante pour la durée de temporisation
C.TEMPO = 0.006
-- Constantes pour l'origine de la rotation à gauche, au centre ou à droite du modèle
C.ROTATION_ORIGINE_GAUCHE = 1
C.ROTATION_ORIGINE_CENTRE = 2
C.ROTATION_ORIGINE_DROITE = 3
-- Constantes pour le sens du mouvement de la rotation X et Z
C.VERS_LARRIERE = 5
C.VERS_LAVANT = 6
-- Constantes pour déplacer l'origine de la rotation Y
C.ROT_Y_BORD_SUP = 10
C.ROT_Y_BORD_INF = 11
C.ROT_Y_A_GAUCHE = 12
C.ROT_Y_A_DROITE = 13
-- Constantes pour déplacer l'origine de la rotation Z
C.ROT_Z_COIN_AVANT_GAUCHE = 20
C.ROT_Z_MILIEU_GAUCHE = 21
C.ROT_Z_COIN_ARRIERE_GAUCHE = 22
C.ROT_Z_COIN_AVANT_DROIT = 25
C.ROT_Z_MILIEU_DROIT = 26
C.ROT_Z_COIN_ARRIERE_DROIT = 27
-- Constantes pour tester l'emplacement des rotations de base des modèles
C.ROT_X = 30
C.ROT_Y = 31
C.ROT_Z = 32
-- Constantes pour indiquer l'axe concernée pour la rotation
C.ROTATION_AXE_X = 40
C.ROTATION_AXE_Y = 41
C.ROTATION_AXE_Z = 42
C.ROTATION_BASE = 45
-- Constantes pour les opérations d'ouverture et de fermeture
C.OUVERTURE = 50
C.FERMETURE = 51
return C
Une constante comme son nom l'indique est (pour schématiser) un emplacement dans la mémoire dont la valeur ne change jamais. En Lua, il n'existe pas de mot-clé spécifique pour déclarer des constantes comme dans d'autres langages de programmation. Cependant, nous pouvons simuler des constantes en utilisant des conventions de nommage et en évitant de modifier les valeurs une fois qu'elles sont définies. La convention de nommage la plus utilisée pour les constantes est de les écrire en majuscules.
A part les constantes C.PI180 et C.TEMPO, toutes les valeurs affectées aux autres constantes à partir de la ligne n° 9 sont totalement arbitraires. L' impératif est de ne pas avoir la même valeur pour deux constantes différentes !
Les constantes sont largement utilisées dans les arguments des fonctions et dans les tests conditionnels. Les deux avantages principaux sont :
- Associer une valeur unique à une constante permet de nommer précisément l'action à effectuer et la lecture du code s'en trouve simplifiée,
- Comme les valeurs sont uniques, aucun risque d'erreur qui pourrait engendrer des bugs inutiles.
Pour résumer
Un petit schéma rendu à sa plus simple expression vaut mieux qu'un long discours :
L'organigramme ci-dessus montre l’interaction entre les différents fichiers Lua. En magenta, les deux fichiers Projet_principal.lua et ID_Modeles.lua interagissent à travers leurs fonctions respectives. En bleu, le fichier Const_Var.lua ne contient pas de fonctions, il se contente de communiquer les valeurs des constantes lorsque les fonctions des deux fichiers en ont besoin. Naturellement, tout ce processus est totalement transparent pour l'utilisateur.
Présentation de la rotation Z
La rotation Z permet de garder le modèle dans sa position Z d'origine. Rappelez-vous comme nous l'avons déjà vu dans le premier article, l'axe de position Z ne bouge pas si vous manipulez la rotation Z ce qui est logique vu que que la rotation s'effectue autour de l'axe du même nom :
Par exemple, la rotation Z pourrait très bien être utilisée pour ouvrir ou fermer une porte, gérer un accès à un entrepôt, etc...
De nos jours, les constructeurs pour EEP utilisent des logiciels de modélisation 3D et par conséquent, ne placent pas forcément le centre de leurs modèles sur les positions X, Y, Z à 0, 0, 0 dans leur logiciel. Voilà pourquoi une fois dans EEP, en fonction de la conception des modèles, le point de rotation Z en utilisant uniquement les fonctions Lua pour EEP peut se retrouver aussi bien sur le côté gauche, sur le côté droit ou au centre.
Maintenant vous avez l'explication pour les modèles présentés dans le premier article. Dans ces trois exemples ci-après, la fonction EEPStructureSetRotation() a été utilisée dans une simple boucle numérique et pourtant nous avons trois comportements différents :
Pour rappel avec le gizmo, la rotation avec la souris s'effectuera toujours au centre mais à partir de maintenant, oublions le gizmo pour nous concentrer uniquement sur le script et les fonctions qu'il contient.
Présentation des variables globales du projet
Je vais maintenant vous faire découvrir les variables et tables globales déclarées au début du fichier Projet_principal.lua juste après la fonction Lua require de la ligne n° 19 :
--[[ En préparation pour l'ouverture du fichier projet en cours dans EEP
Récupérer le nom du projet via la fonction lua EEPGetAnlName()
(EEPGetAnlName nécessite EEP17 plugin 1 sinon écrire en dur le nom du projet
dans la variable gSt_NomProjet sans rajouter d'anti-slash)
]]
gSt_NomProjet = EEPGetAnlName()
-- Table pour récupérer la longueur en mètres sur l'axe X des modèles si modification d'échelle
gTb_Ech_Dimension_X = {}
-- Table pour récupérer la largeur en mètres sur l'axe Y des modèles si modification d'échelle
gTb_Ech_Dimension_Y = {}
-- Table pour récupérer la hauteur en mètres sur l'axe Z des modèles si modification d'échelle
gTb_Ech_Dimension_Z = {}
-- Table pour stocker La longueur X des modèles à l'échelle 1
gTb_Long_X_Ech1 = {}
-- Table pour stocker La largeur Y des modèles à l'échelle 1
gTb_Larg_Y_Ech1 = {}
-- Table pour stocker La hauteur Z des modèles à l'échelle 1
gTb_Haut_Z_Ech1 = {}
-- Table pour stocker la rotation d'origine (de base) à gauche, à droite ou au centre
-- par rapport à la conception du modèle
gTb_Rot_Origine = {}
-- Incrémentation à chaque itération de la boucle (valeur en degré)
gTb_Rot_Increment = {}
-- Table pour stocker les valeurs angulaires minimales à atteindre en degrés pour les modèles
gTb_Rot_Min = {}
-- Table pour stocker les valeurs angulaires maximales à atteindre en degrés pour les modèles
gTb_Rot_Max = {}
-- Table pour stocker la valeur calculée des rotations des modèles à chaque itération de la boucle while
gTb_Rot_Calcul = {}
-- Table pour corriger les positions (si modification des échelles) selon l'orientation des modèles dans le plan
gTb_Corr_Pos_X = {}
gTb_Corr_Pos_Y = {}
gTb_Corr_Pos_Z = {}
-- Déclarer 3 tables pour récupérer les positions des modèles
Bool_Ok, gTb_Pos_X, gTb_Pos_Y, gTb_Pos_Z = nil, {}, {}, {}
-- Déclarer 3 tables pour récupérer les rotations des modèles
Bool_Ok, gTb_Rot_X, gTb_Rot_Y, gTb_Rot_Z = nil, {}, {}, {}
Le préfixe g_ est une règle personnelle pour nommer les variables et les tables au niveau global. Vous êtes aussi libre d'adopter si vous le souhaitez vos propres règles de nommage. Nous pouvons remarquer des accolades après le signe =, ce qui signifie que toutes ces tables vont stocker des valeurs sous forme de... table (array en anglais). Une table peut être comparée à un tableau avec des lignes et des colonnes (j'aborde ce sujet un peu plus bas). Toutes ces tables sont globales, c'est-à-dire qu'elles sont visibles et accessibles à l'intérieur de toutes les fonctions du script et peu importe les fichiers Lua qui les utilisent.
Analyse du processus pour l'ouverture
Activation des contacts
Pour commencer admettons que nous voulons utiliser un mur (parce que le modèle s'y prête) pour faire office d'ouverture et de fermeture. Si la rotation de base du modèle est située à gauche comme le mur dans l'exemple ci-dessous, pas de problème, un simple appel à la fonction EEPStructureSetRotation() fera largement l'affaire. Démonstration en vidéo :
Ici la rotation de base pour ce mur est fixée à gauche. Comment pouvons-nous le savoir ? très simplement avec une boucle numérique, comme ceci :
-- Récupérer les angles de rotation du modèle ID25
Bool_Ok, X, Y, Z = EEPStructureGetRotation("#25")
-- Ouverture de 0 à 90 degrés en rotation Z
for i = 0, 90 do
EEPStructureSetRotation( "#25", X, Y, i )
end
-- Fermeture de 90 à 0 degrés en rotation Z
for i = 90, 0, -1 do
EEPStructureSetRotation( "#25", X, Y, i )
end
Une fois ce petit test terminé, nous pouvons déterminer visuellement le point de rotation réel du modèle (qui je le répète encore une dernière fois n'a rien à voir avec celle du gizmo). Dans la vidéo, la boucle utilisée est conçue sur le même principe.
Mais pour le moment, nous n'allons pas utiliser la fonction EEPStructureSetRotation() tout de suite. Nous allons d'abord poser les bases du script. Cette étape est importante pour la suite.
Pour trouver le n° d'identification d'un modèle en mode édition 3D, faites un clic droit sur le modèle pour ouvrir le menu contextuel et choisissez la commande Propriétés de l'objet pour ouvrir la fenêtre des propriétés de l'objet :
Seul le signe # et le numéro sont importants. Tout ce qui suit après le trait de soulignement ( _ ) peut être ignoré.
Les fonctions fn_Contact_Open_ID25() et fn_Contact_Close_ID25() sont appelées par des contacts posés sur la voie et déclenchés par la locomotive rouge. Les noms de ces deux fonctions (sans les parenthèses) figurent dans les propriétés des contacts :
Ces deux fonctions sont situées respectivement dans le fichier Projet_principal.lua aux lignes n° 696 et 702.
Le point de départ de la séquence commence donc à la ligne n° 696 du script principal et concerne le modèle avec le n° ID25 :
-- Ouverture du mur ID25
function fn_Contact_Open_ID25()
Mod.Ouvre_ID25("#25")
end
-- Fermeture du mur ID25
function fn_Contact_Close_ID25()
Mod.Ferme_ID25("#25")
end
Lecture des paramètres du modèle
Lors de l'activation des contacts, Les lignes n° 696 et 702 dans l'extrait du code ci-dessus appellent les deux fonctions intitulées Mod.Ouvre_ID25("#25") à la ligne n° 698 et Mod.Ferme_ID25("#25") à la ligne n° 704.
Note : Vous remarquerez dans le fichier du Projet_principal.lua l'utilisation (à la ligne n° 698) de la variable globale Mod (déclarée en haut du fichier à la ligne n° 16). Pourtant dans le fichier ID_Modeles.lua ci-dessous, Mod se transforme en M à la ligne n° 1073. Pourquoi ? la raison est la suivante : toutes les fonctions du fichier ID_Modeles.lua sont transférées dans une table nommée M au sein même de ce fichier. Cette table renvoie tout son contenu dans la variable Mod à la ligne n° 16 du script principal via l'instruction require. Voilà pourquoi dans le script principal il faut utiliser la variable Mod.
Ces deux fonctions sont incluses dans le fichier ID_Modeles.lua :
-- Ouverture du mur ID25
function M.Ouvre_ID25(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBetonRE1 pour récupérer les paramètres communs du mur en béton
M.ImmoBetonRE1(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 ID25
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.OUVERTURE)
end
-- Fermeture du mur ID25
function M.Ferme_ID25(Ar_ID_Lua)
-- Valeur minimale à atteindre en degrés pour le mur ID25
gTb_Rot_Min[Ar_ID_Lua] = 0
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.FERMETURE)
end
Et pour terminer à la ligne n° 1076, la fonction M.ImmoBetonRE1(Ar_ID_Lua) est appelée à son tour pour récupérer les paramètres communs du modèle utilisés dans les opérations d'ouverture et de fermeture. Voici à la ligne n° 2029, la fonction M.ImmoBetonRE1(Ar_ID_Lua) en question :
-- =====================================================================
-- = =
-- = Modèle : Mur en béton (Paroi de la voie) du constructeur RE1 =
-- = M.ImmoBetonRE1 concerne les modèles avec les ID : =
-- = 20, 21, 23, 25, 19, 39, 63, 64, 65, 100, 101, 102, 112, 113 =
-- = =
-- =====================================================================
-- Paramètres communs pour le mur en béton
function M.ImmoBetonRE1(Ar_ID_Lua)
-- La longueur du mur Ar_ID_Lua à l'échelle 1 est = à 10.5 mètres
gTb_Long_X_Ech1[Ar_ID_Lua] = 10.5
-- La largeur du mur Ar_ID_Lua à l'échelle 1 est = à 6 mètres
gTb_Larg_Y_Ech1[Ar_ID_Lua] = 6
-- La hauteur du mur Ar_ID_Lua à l'échelle 1 est = à 8 mètres
gTb_Haut_Z_Ech1[Ar_ID_Lua] = 8
--[[ 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_GAUCHE
end
Comme vous pouvez le constater dans les commentaires, cette fonction est appelée autant de fois qu'il y a de modèles répertoriés par leurs numéros ID d'où l'évidence de rassembler tous les paramètres du modèle à un seul endroit pour éviter les répétitions comme expliqué plus haut. Nous allons maintenant détailler le contenu :
Les lignes n° 2053-2057 contiennent des commentaires bien utiles pour documenter le code et sont totalement ignorés lors de l'exécution du script. A la ligne n° 2062, nous avons l'en-tête de la fonction constitué de son nom et d'un argument entre parenthèse qui contient ici le n° ID du modèle concerné.
Aux lignes 2065, 2067 et 2069, nous renseignons dans les trois tables gTb_Long_X_Ech1, gTb_Larg_Y_Ech1 et gTb_Haut_Z_Ech1 les dimensions du modèle à l'échelle 1, j'attire votre attention sur ces valeurs indispensables pour la bonne exécution des opérations par la suite.
La ligne n° 2072 nous fait découvrir la table gTb_Rot_Calcul[Ar_ID_Lua] dans laquelle sera enregistrée le calcul pour la valeur de la rotation et pour terminer à la ligne n° 2074, la dernière table gTb_Rot_Origine[Ar_ID_Lua] nous informe avec la constante C.ROTATION_ORIGINE_GAUCHE où se situe la rotation d'origine (de base) du modèle. Ici en l'occurrence il s'agit du coté gauche.
Sémantique des arguments
Maintenant, nous allons isoler les lignes concernant les appels des fonctions pour s'intéresser à l'argument entre parenthèses :
Mod.Ouvre_ID25("#25")
Mod.Ferme_ID25("#25")
function M.Ouvre_ID25(Ar_ID_Lua)
function M.Ferme_ID25(Ar_ID_Lua)
function M.ImmoBetonRE1(Ar_ID_Lua)
Aux lignes n° 702 et 708, il suffit d'inscrire le n° ID du modèle concerné dans le format attendu par les fonctions Lua pour EEP. Pour rappel, les fonctions EEPStructureSetRotation() et EEPGoodsSetRotation() s'attendent à recevoir comme premier argument le n° ID sous le format "#numéro", c'est-à-dire le numéro sous la forme d'une chaine de caractères.
Aux lignes n° 1073, 1086 et 2062, les fonctions récupèrent le n° ID sous la forme d'un argument qui pourrait se traduire par : "Moi Ar_ID_Lua ! j'ai enregistré le n° ID "#25" qui m'a été communiqué lorsque ma fonction a été appelée".
Vous pouvez éventuellement vous référer au chapitre Focus sur la construction des fonctions pour comprendre ce mécanisme si vous n'êtes pas familiarisé avec les liens entre les appels des fonctions et les arguments qui y sont rattachés.
Ainsi, toutes les fonctions communiquent entre-elles le numéro ID du modèle (de vraies bavardes !) et ce numéro peut ensuite être utilisé à l'intérieur des fonctions pour des travaux à exécuter.
Analyse du processus pour l'opération de l'ouverture
Initialisation des paramètres
Bon tout ceci est bien joli, les paramètres du modèle sont récupérés, mais il nous manque encore les opérations pour l'ouverture et la fermeture de notre "mur-porte " ! Pour rappel, ce processus débute lorsque la fonction Mod.Ouvre_ID25("#25") à la ligne n° 702 est appelée une fois le contact activé par la locomotive via la fonction fn_Contact_Open_ID25() dans le script principal :
-- Ouverture du mur ID25
function fn_Contact_Open_ID25()
Mod.Ouvre_ID25("#25")
end
A la ligne n° 1076, la fonction M.ImmoBetonRE1(Ar_ID_Lua) déjà vue plus haut est appelée :
-- Ouverture du mur ID25
function M.Ouvre_ID25(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBetonRE1 pour récupérer les paramètres communs du mur en béton
M.ImmoBetonRE1(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 ID25
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.OUVERTURE)
end
Passons maintenant à la ligne n° 1078 avec la table gTb_Rot_Increment[Ar_ID_Lua]. Comme son nom l'indique, elle détermine l'incrémentation nécessaire au calcul à chaque itération d'une boucle que nous allons bientôt découvrir et enfin, la ligne n° 1080 concerne la table gTb_Rot_Max[Ar_ID_Lua] renseignée avec l'angle maximal pour l'ouverture qu'il ne faudra en aucun jamais dépasser. Ici l'angle est négatif.
Fonction pour l'opération d'ouverture - Introduction
Nous arrivons enfin au plus important, l'appel de la fonction fn_Operation(.....) à la ligne n° 1082 !
fn_Operation(Ar_ID_Lua, C_.ROTATION_BASE, nil, C_.ROT_Z, C.OUVERTURE)
Avant de découvrir cette fonction, intéressons-nous aux cinq arguments entre les parenthèses :
- Le premier argument contient le n° Id du modèle,
- Le deuxième argument est paramétré avec la constante C_.ROTATION_BASE car nous choisissons la rotation de base pour le modèle,
- Le troisième argument est pour le moment paramétré à nil et ignoré donc inutile de s'y attarder maintenant,
- Le quatrième argument est paramétré avec la constante C_.ROT_Z car nous allons demander une rotation Z à notre fonction,
- Le cinquième argument indique à la fonction de traiter l'opération pour l'ouverture.
Alors où se trouve la fonction fn_Operation(.....) ? Tout simplement dans le fichier Projet_principal.lua.
Pour se familiariser avec celle-ci, nous allons débuter par les commentaires placés dans l'en-tête de cette fonction :
-- *************************************************************************************************
-- * *
-- * La fonction fn_Operation se charge des trois rotations X, Y, Z pour pivoter les structures *
-- * vers l'avant et vers l'arrière. *
-- * *
-- * Liste des arguments : *
-- * *
-- * Ar_ID_Lua = N° ID Lua de l'objet *
-- * Ar_Rot_Souhait = Rotation souhaitée (x, y, z ou de base) *
-- * Ar_Sens_Mouvement = Sens du mouvement avant ou arrière des modèles *
-- * concerne uniquement les rotation X et Z *
-- * Ar_Pos_Pt_Rot* = Rotation Z (détermine si la rotation s'effectue au milieu, en *
-- * arrière ou en avant sur l'axe Y du modèle) *
-- * Rotation Y (détermine si la rotation s'effectue à gauche ou à droite) *
-- * -> C.ROT_Z_COIN_AVANT_GAUCHE = 20 *
-- * -> C.ROT_Z_MILIEU_GAUCHE = 21 *
-- * -> C.ROT_Z_COIN_ARRIERE_GAUCHE = 22 *
-- * -> C.ROT_Z_COIN_AVANT_DROIT = 25 *
-- * -> C.ROT_Z_MILIEU_DROIT = 26 *
-- * -> C.ROT_Z_COIN_ARRIERE_DROIT = 27 *
-- * -> C.ROT_X = 30 *
-- * -> C.ROT_Y = 31 *
-- * -> C.ROT_Z = 32 *
-- * -> C.ROT_Y_A_GAUCHE = 12 *
-- * -> C.ROT_Y_A_DROITE = 13 *
-- * *
-- * * Argument ignoré pour les rotations X et de base *
-- * *
-- * Ar_Operation = Type d'opération (ouverture ou fermeture) *
-- * *
-- *************************************************************************************************
Les lignes n° 1252 à 1273 détaillent les arguments passés à la fonction. Comme vous pouvez le constater, celle-ci nous ouvre un champ de possibilités qui serait totalement irréalisable sans l'aide des fonctions Lua.
Pour le moment, je vais garder uniquement les lignes pertinentes pour la suite de cet article :
function fn_Operation(Ar_ID_Lua, Ar_Rot_Souhait, Ar_Sens_Mouvement, Ar_Pos_Pt_Rot, Ar_Operation)
La ligne n° 1276 contient entre parenthèses les cinq arguments passés lors de l'appel de la fonction à partir du fichier ID_Modeles.lua :
Fonction M.Ouvre_ID25(Ar_ID_Lua)
-- Ouverture du mur ID25
function M.Ouvre_ID25(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBetonRE1 pour récupérer les paramètres communs du mur en béton
M.ImmoBetonRE1(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 ID25
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.OUVERTURE)
end
Ainsi nous retrouvons :
- Le premier argument avec le n° Id du modèle,
- Le deuxième argument concerne la rotation souhaitée. Ici nous voulons la rotation de base, voilà pourquoi la constante C.ROTATION_BASE avait été utilisée comme argument lors de l'appel,
- Le troisième argument ne sera pas utilisé ici donc aucune raison de s'y attarder,
- Le quatrième argument concerne le type de rotation, voilà pourquoi la constante C.ROT_Z avait été utilisée comme argument lors de l'appel,
- Le cinquième argument concerne l'opération à réaliser, ici il s'agit de l'ouverture.
Continuons la découverte de la fonction :
-- /** 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
Juste après l'en-tête de la fonction, voici la déclaration des variables et table locales. Ces variables seront utilisées durant toute la durée d'exécution de la fonction et détruites une fois celle-ci terminée. Les commentaires sont là pour vous donner quelques explications mais nous aurons l'occasion de revenir sur ces variables dans cet article et les suivants.
Continuons la découverte :
-- 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 )
Toutes ces variables globales ont été déclarées à partir de la ligne n° 62 du script principal :
-- Déclarer 3 tables pour récupérer les positions des modèles
Bool_Ok, gTb_Pos_X, gTb_Pos_Y, gTb_Pos_Z = nil, {}, {}, {}
-- Déclarer 3 tables pour récupérer les rotations des modèles
Bool_Ok, gTb_Rot_X, gTb_Rot_Y, gTb_Rot_Z = nil, {}, {}, {}
Ici nous déclarons simplement une variable et trois tables sur une seule ligne. Ci-dessous les explications basées sur le même principe pour la position et la rotation :
- La première variable Bool_Ok va récupérer la valeur concernant la bonne exécution ou non des fonctions EEPStructureGetPosition / EEPStructureGetRotation. Cette variable est déclarée avec la valeur nil (nil = valeur nulle en langage lua),
- La deuxième table gTb_Pos_X / gTb_Rot_X va récupérer la valeur de la position X / rotation X renvoyée par les fonctions EEPStructureGetPosition / EEPStructureGetRotation. Cette variable est déclarée comme une table,
- La troisième table gTb_Pos_Y / gTb_Rot_Y va récupérer la valeur de la position Y / rotation Y renvoyée par les fonctions EEPStructureGetPosition / EEPStructureGetRotation. Cette variable est déclarée comme une table,
- La troisième table gTb_Pos_Z / gTb_Rot_Z va récupérer la valeur de la position Z / rotation Z renvoyée par les fonctions EEPStructureGetPosition / EEPStructureGetRotation. Cette variable est déclarée comme une table.
Je vais faire un bref rappel concernant les tables. Une table n'est ni plus ni moins qu'un tableau avec des lignes et des colonnes, ci-dessous un petit schéma pour faciliter la compréhension :
Ainsi chaque modèle identifié par son n° Ar_ID_Lua possède son propre espace pour sauvegarder en mémoire tous ses paramètres sans ambiguité avec les autres modèles.
Bien entendu, toutes les variables déclarées en tant que tables au début du script principal fonctionnent exactement selon le même principe. Par exemple :
- Si Ar_ID_Lua est égal à #26 alors gTb_Rot_X[Ar_ID_Lua] est = à -58.00,
- Si Ar_ID_Lua est égal à #29 alors gTb_Pos_Y[Ar_ID_Lua] est = à 127.36,
- Si Ar_ID_Lua est égal à #202 alors gTb_Rot_Z[Ar_ID_Lua] est = à -135.00,
- Si Ar_ID_Lua est égal à #2 alors gTb_Pos_Y[Ar_ID_Lua] est = à 105.68,
- Si Ar_ID_Lua est égal à #304 alors gTb_Pos_X[Ar_ID_Lua] est = à 94.36,
- Et ainsi de suite...
Cette gestion des tables est à mettre en parallèle avec quelques avantages non négligeables :
- L' utilisateur n'a pas à s'occuper de l'organisation interne ni le classement de la table,
- Rapidité d'exécution car il suffit d'interroger la table concernée avec le numéro ID pour retrouver immédiatement la valeur correspondante,
- Aucun risque de confusion entre les modèles vu que chaque modèle possède son numéro ID unique,
- Inutile de déclarer autant de variables différentes dédiées pour chaque modèle que l'on veut tester,
- Les modèles n'ont pas besoin d'être contigus par leurs numéros ID, il peut y avoir des "trous" dans la colonne ID_Lua.
Si vous êtes familiarisés avec d'autres langages, une table Lua devient un tableau ou array en Javascript, un array en php, un tableau en Vb, etc...
Juste pour info, ici nous avons une table associative. Dans notre projet, tous les numéros ID des modèles correspondants sont associés aux tables.
Fonction pour l'opération d'ouverture - Développement
Revenons au début de notre fonction fn_Operation(.....) :
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 soit la valeur maximale ou minimale selon l'ouverture ou la fermeture
local Nb_Rotation_Min_Max = 0
-- Les calculs s'effectuent uniquement lors 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 )
Les lignes 1294 et 1296 récupèrent les positions et les rotations d'origine X, Y et Z du modèle concerné pour les besoins des calculs par la suite.
Passons directement à la ligne n° 1363 et le test conditionnel réalisée avec l'instruction if...... then. Ici nous allons tester la valeur du deuxième argument Ar_Rot_Souhait dans l'en-tête de la fonction :
-- /** Si la rotation souhaitée concerne la rotation X **/
if Ar_Rot_Souhait == C.ROTATION_AXE_X then
Pour connaitre cette valeur, rien de plus facile, il suffit de regarder quelle constante avait été utilisée lors de l'appel de la fonction M.Ouvre_ID25(Ar_ID_Lua) dans le fichier ID_Modeles.lua à la ligne 1082 :
Fonction M.Ouvre_ID25(Ar_ID_Lua)
-- Ouverture du mur ID25
function M.Ouvre_ID25(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBetonRE1 pour récupérer les paramètres communs du mur en béton
M.ImmoBetonRE1(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 ID25
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.OUVERTURE)
end
Nous retrouvons en deuxième position la constante C.ROTATION_BASE, donc l'argument Ar_Rot_Souhait contient la valeur de la constante C.ROTATION_BASE. Dans ce cas, la comparaison effectuée par notre test à la ligne n° 1363 peut-elle se réaliser ? Est-ce que Ar_Rot_Souhait est égal à C.ROTATION_BASE ? Bien sûr que non car l'égalité testée est si Ar_Rot_Souhait est égale à C.ROTATION_AXE_X (vous remarquerez le signe double == pour tester l'égalité des valeurs). Comme la comparaison est fausse, la ligne située immédiatement après la ligne n° 1364 est purement et simplement ignorée. Le programme va "sauter" directement à la prochaine condition de test qui est affichée ci-dessous à la ligne 1463 :
-- /** Sinon si la rotation souhaitée concerne la rotation Y **/
elseif Ar_Rot_Souhait == C.ROTATION_AXE_Y then
Ici, même déduction avec la constante C.ROTATION_AXE_Y. Nous savons maintenant que la valeur du deuxième argument Ar_Rot_Souhait est égal à C.ROTATION_BASE. La comparaison est fausse et le programme va continuer jusqu'à la prochaine condition à tester qui se trouve à la ligne n° 1582 :
-- /** Sinon si la rotation souhaitée concerne la rotation Z **/
elseif Ar_Rot_Souhait == C.ROTATION_AXE_Z then
Comme ci-dessus. La valeur du deuxième argument Ar_Rot_Souhait est égal à C.ROTATION_BASE et non pas à C.ROTATION_AXE_Z. La comparaison est fausse et le programme va continuer jusqu'à la prochaine condition à tester qui se trouve à la ligne n° 1772 :
-- /** Sinon si la rotation souhaitée concerne la rotation de base **/
elseif Ar_Rot_Souhait == C.ROTATION_BASE then
Enfin ! nous testons l'égalité si Ar_Rot_Souhait est égale à C.ROTATION_BASE ce qui est maintenant le cas et va permettre désormais au programme de continuer à la ligne située immédiatement après. Nous allons ainsi pouvoir examiner la ligne suivante :
if Ar_Operation == C.OUVERTURE then
Comme nous sommes dans l'opération d'ouverture, la condition va être vérifiée et le programme va continuer à la ligne juste en dessous :
-- Par défaut, l'angle d'ouverture des modèles avec la rotation de base est positif
Nb_Rotation_Min_Max = math.abs( gTb_Rot_Max[Ar_ID_Lua] )
A la ligne n° 1777, la fonction Lua math.abs a été utilisée pour convertir la valeur de la table gTb_Rot_Max[ID_Lua] en valeur positive. Le résultat va ensuite être transféré dans la variable Nb_Rotation_Min_Max.
Premièrement, quelle est la valeur de la table gTb_Rot_Max[ID_Lua] à la ligne n° 1080 ?
Fonction M.Ouvre_ID25(Ar_ID_Lua)
-- Ouverture du mur ID25
function M.Ouvre_ID25(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBetonRE1 pour récupérer les paramètres communs du mur en béton
M.ImmoBetonRE1(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 ID25
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.OUVERTURE)
end
La valeur initiale est de -88°. La fonction Lua math.abs va donc convertir -88° en 88°. Pour info, une valeur absolue est toujours une valeur positive ! Alors pourquoi d'une valeur négative passer à une valeur positive ?
Réponse : Par convention dans ce script, toutes les valeurs maximales pour les rotations sont négatives. La logique serait de dire : "les angles négatifs concernent uniquement les ouvertures vers l'arrière et les angles positifs uniquement les ouvertures vers l'avant". Et bien non, car nous allons voir dans un prochain article qu'une rotation arrière peut exiger des angles positifs ou négatifs selon la rotation demandée, le sens d'ouverture et surtout selon la conception du modèle. Dans un souci d'uniformiser le code, toutes les valeurs angulaires pour les ouvertures sont négatives. A charge dans les parties du code dédiées aux tests de traiter selon le cas, la valeur négative ou positive de la rotation.
Pour aller plus loin : Si j'ai opté pour une valeur négative, c'est qu'il n'existe pas dans le langage Lua, une fonction qui permet de retourner un nombre négatif comme le permet la fonction math.abs pour les nombres positifs même si cela est tout à fait réalisable avec une fonction personnalisée comme par exemple :
function Nbr_Negatif(num)
return -math.abs(num)
end
Mais je n'ai pas retenu cette solution pour ne pas alourdir le code inutilement.
Maintenant nous en avons presque terminé avec la fonction ouverture, Il nous reste à connaitre quelle rotation à appliquer. La réponse se situe à la ligne n° 1082 dans le quatrième argument de notre fonction M.Ouvre_ID25(Ar_ID_Lua) située dans le fichier ID_Modeles.lua (voir dans le bandeau ci-dessus).
La constante dans le quatrième argument s'appelle C.ROT_Z. Cette valeur est récupérée via le quatrième argument Ar_Pos_Pt_Rot dans le corps de la fonction fn_Operation(.....) à la ligne 1276 dans le fichier Projet_principal.lua :
function fn_Operation(Ar_ID_Lua, Ar_Rot_Souhait, Ar_Sens_Mouvement, Ar_Pos_Pt_Rot, Ar_Operation)
Il nous reste juste à tester quelle partie du code doit être exécutée exactement comme nous l'avons fait plus haut pour déterminer la rotation de base :
if Ar_Pos_Pt_Rot == C.ROT_X then
-- Tant que la valeur calculée de la rotation est inférieure ou égale à la valeur absolue de la rotation max... la boucle continue
while gTb_Rot_Calcul[Ar_ID_Lua] <= Nb_Rotation_Min_Max do
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] + gTb_Rot_Increment[Ar_ID_Lua]
-- Applique la rotation X au modèle
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_Calcul[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Z[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
elseif Ar_Pos_Pt_Rot == C.ROT_Y then
-- Idem que pour C.ROT_X (voir lignes 1856 et 1857)
while gTb_Rot_Calcul[Ar_ID_Lua] <= Nb_Rotation_Min_Max do
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] + gTb_Rot_Increment[Ar_ID_Lua]
-- Applique la rotation Y au modèle
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua], gTb_Rot_Z[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
elseif Ar_Pos_Pt_Rot == C.ROT_Z then
-- Idem que pour C.ROT_X (voir lignes 1856 et 1857)
while gTb_Rot_Calcul[Ar_ID_Lua] <= Nb_Rotation_Min_Max do
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] + gTb_Rot_Increment[Ar_ID_Lua]
-- Applique la rotation Z au modèle
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
end
Comme nous avons demandé une rotation Z, les conditions retournées pour l'égalité des tests conditionnels aux lignes n° 1779 et 1792 seront fausses, ce qui nous amène tout naturellement à la ligne n° 1805. Ici, l'égalité est vraie car Ar_Pos_Pt_Rot est = à C.ROT_Z. Le bloc de code situé entre les lignes n° 1808 et 1816 va pouvoir être exécuté. Rentrons dans le détail :
La ligne n° 1808 démarre une boucle while qui veut dire : tant que le calcul de la rotation est inférieur ou égal ( <= ) à la valeur angulaire maximale que l'on désire obtenir, la condition est vérifiée et vraie ainsi les lignes n° 1810 à 1814 sont exécutées puis la ligne n° 1816 renvoie à la ligne n° 1808 et le cyle recommence. On applique simplement le principe des boucles conditionnelles.
Le théorie est simple : tant que la condition reste vraie et bien la boucle recommence encore et encore. Inversement, la condition devient fausse lorsque le calcul de la rotation devient supérieur à la valeur angulaire maximale. Cela signifie que nous avons atteint l'angle maximal pour l'ouverture souhaitée. Dans ce cas la ligne n° 1816 ne renvoie plus à la ligne n° 1808 mais passe immédiatement à la suite, c'est-à-dire à la ligne 1818 car la boucle est terminée.
Résumons : à la ligne n° 1808 lors de la toute première itération de la boucle, la valeur de la table gTb_Rot_Calcul[Ar_ID_Lua] est = à 0. Cette valeur a été définie dans les paramètres communs du modèle à la ligne n° 2039 dans le fichier ID_Modeles.lua :
Rappel du fichier ID_Modeles.lua (fonction M.ImmoBetonRE1)
-- Paramètres communs pour le mur en béton
function M.ImmoBetonRE1(Ar_ID_Lua)
-- La longueur du mur Ar_ID_Lua à l'échelle 1 est = à 10.5 mètres
gTb_Long_X_Ech1[Ar_ID_Lua] = 10.5
-- La largeur du mur Ar_ID_Lua à l'échelle 1 est = à 6 mètres
gTb_Larg_Y_Ech1[Ar_ID_Lua] = 6
-- La hauteur du mur Ar_ID_Lua à l'échelle 1 est = à 8 mètres
gTb_Haut_Z_Ech1[Ar_ID_Lua] = 8
--[[ 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_GAUCHE
end
A chaque itération de la boucle, la valeur de la table gTb_Rot_Calcul[Ar_ID_Lua] augmente avec la valeur de la table gTb_Rot_Increment[Ar_ID_Lua] (ici paramétrée à 0.2 degré). Cette valeur est paramétrée à la ligne n° 1078 dans le fichier ID_Modeles.lua :
Rappel du fichier ID_Modeles.lua (fonction M.Ouvre_ID25)
-- Ouverture du mur ID25
function M.Ouvre_ID25(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBetonRE1 pour récupérer les paramètres communs du mur en béton
M.ImmoBetonRE1(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 ID25
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.OUVERTURE)
end
Maintenant nous avons tous les éléments en main et nous pouvons dès à présent appliquer la rotation Z au modèle :
while gTb_Rot_Calcul[Ar_ID_Lua] <= Nb_Rotation_Min_Max do
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] + gTb_Rot_Increment[Ar_ID_Lua]
-- Applique la rotation Z au modèle
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
La ligne n° 1808 ne fait qu'utiliser la fonction EEPStructureSetRotation() avec tous les arguments nécessaires pour le bon déroulement de l'opération avec la table gTb_Rot_Calcul[Ar_ID_Lua] placée au bon endroit. Nous retrouvons également nos tables gTb_Rot_X[Ar_ID_Lua] et gTb_Rot_Y[Ar_ID_Lua] avec les valeurs d'origine. Eh oui ! nous modifions uniquement la rotation Z, nous devons laisser intactes les autres rotations.
La ligne n° 1814 fait appel à une fonction personnalisée nommée fn_Tempo. Nous allons y revenir après.
A un moment donné, inéluctablement, la valeur de la table gTb_Rot_Calcul[Ar_ID_Lua] va être supérieure à la valeur angulaire maximale (variable Nb_Rotation_Min_Max). La condition devient fausse et ainsi notre boucle va se terminer automatiquement.
Fonction pour l'opération d'ouverture - Conclusion
Maintenant la porte doit maintenant être complètement ouverte avec un angle de 88°. Nous allons le vérifier en vidéo :
Et en image dans les propriétés de l'objet :
Nous constatons bien l'angle de rotation Z fixé à 88° ainsi que le n° ID du modèle qui est le n° 25.
Important : à ce stade, la valeur de la table gTb_Rot_Calcul[Ar_ID_Lua] est égale à la valeur angulaire maximale. Cette donnée doit être conservée car elle sera nécessaire pour la fermeture. Ceci explique pourquoi cette valeur est conservée dans une table globale. Elle reste accessible à n'importe quel endroit du programme. Par conséquent, toutes les fonctions peuvent l'interroger.
Au final, tous les modèles peuvent avoir des rotations différentes, chaque calcul sera soigneusement répertorié dans sa propre ligne grâce à l'argument Ar_ID_Lua (rappelez-vous le schéma du tableau).
Ouvrir notre "mur-porte" c'est bien ! le refermer c'est mieux ! de plus, le mécanisme pour la fermeture est bien plus simple que l'ouverture ! Mais avant, nous allons découvrir la fonction personnalisée nommée fn_Tempo.
Présentation de la fonction fn_Tempo
La fonction fn_Tempo(Duree) est utilisée pour ralentir la boucle qui gère la rotation aussi bien dans les opérations d'ouverture et de fermeture, sinon le rendu visuel serait complètement irréaliste. La porte s'ouvrirait brutalement de 0 à 88°. Pour s'en convaincre, exemple en vidéo avec la fonction fn_Tempo désactivée :
Voilà pourquoi, nous devons introduire une notion de temporisation exprimée en millisecondes. La temporalité de cette valeur est parfaite pour s'intercaler entre chaque itération de la boucle.
Reprenons le code. L' appel de la fonction fn_Tempo(C_.TEMPO) est située à la ligne n° 1814 à l'intérieur de la boucle afin de ralentir la vitesse d'exécution de celle-ci :
while gTb_Rot_Calcul[Ar_ID_Lua] <= Nb_Rotation_Min_Max do
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] + gTb_Rot_Increment[Ar_ID_Lua]
-- Applique la rotation Z au modèle
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
Dans le fichier Const_Var.lua, la valeur de la constante C.TEMPO est déclarée = à 0.006 seconde, c'est à dire 6 millisecondes :
-- Constante pour les calculs trigonométriques
C.PI180 = math.pi / 180
-- Constante pour la durée de temporisation
C.TEMPO = 0.006
Ci-dessous le code de la fonction fn_Tempo(Duree) :
-- ****************************************************************************
-- * *
-- * Cette fonction ajoute une temporisation entre deux cycles d'exécution *
-- * La durée est exprimée en millisecondes *
-- * *
-- * Cette fonction peut être optimisée avec des bibliothèques Lua externes *
-- * *
-- ****************************************************************************
function fn_Tempo(Ar_Duree)
local Debut = os.clock()
-- On reboucle tant que os.clock() - Debut reste inférieur ou égal à Ar_Duree
while os.clock() - Debut <= Ar_Duree do
end
end
A la ligne n° 2100, on capture la valeur de l'instruction Lua os.clock() dans la variable locale Debut. Pour info, l'instruction os.clock() retourne le temps écoulé (en millisecondes) depuis le démarrage du programme EEP.
Ensuite une boucle conditionnelle calcule la différence entre os.clock() - Debut et la variable Duree. Tant que la valeur de la variable Duree est supérieure à la différence entre (os.clock() - Debut), la condition reste vraie donc la boucle va continuer. Évidemment, l'unité étant la milliseconde, inutile de préciser que l'exécution est extrêmement rapide. Une fois la condition devenue fausse, la boucle se termine et la fonction redonne la main. L' exécution reprend ainsi à la ligne située immédiatement après la ligne n° 1814 dans le fichier Projet_principal.lua.
Le langage Lua ne possède pas de fonction Timer comme Il en existe dans d'autres langages. Pour les puristes, notre fonction fn_Tempo(Duree) n'est pas écrite de manière optimisée mais a le mérite d'être simple à comprendre. On pourrait implémenter une fonction Timer en ajoutant une bibliothèque Lua externe mais cela dépasserait le cadre de cet article et je ne suis pas certain que ce soit possible avec EEP (je ferai des essais à l'occasion).
Analyse du processus pour l'opération de la fermeture
Notre "mur-porte" est resté ouvert. Nous allons le refermer dans sa position initiale. Les calculs ayant été effectués pendant l'opération de l'ouverture, la fermeture est plus simple à appréhender.
Notre fonction fn_Operation a juste besoin d'une donnée supplémentaire : la valeur angulaire minimale enregistrée dans la table gTb_Rot_Min[Ar_ID_Lua] à la ligne n° 1089 dans la fonction M.Ferme_ID25(Ar_ID_Lua) située à la ligne n° 1086 dans le fichier ID_Modeles.lua :
-- Ouverture du mur ID25
function M.Ouvre_ID25(Ar_ID_Lua)
-- Appelle la fonction M.ImmoBetonRE1 pour récupérer les paramètres communs du mur en béton
M.ImmoBetonRE1(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 ID25
gTb_Rot_Max[Ar_ID_Lua] = -88
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.OUVERTURE)
end
-- Fermeture du mur ID25
function M.Ferme_ID25(Ar_ID_Lua)
-- Valeur minimale à atteindre en degrés pour le mur ID25
gTb_Rot_Min[Ar_ID_Lua] = 0
fn_Operation(Ar_ID_Lua, C.ROTATION_BASE, nil, C.ROT_Z, C.FERMETURE)
end
J'ai laissé volontairement la fonction M.Ouvre_ID25(Ar_ID_Lua) pour comparer les deux et en déduire que M.Ferme_ID25(Ar_ID_Lua) n'a plus besoin de :
- Relire à nouveau les paramètres communs du modèle (ligne n° 1076) car ceux-ci sont déjà enregistrés dans les tables prévues à cet effet lors de l'opération d'ouverture,
- Relire à nouveau la table pour l'incrémentation de la boucle (ligne n° 1078).
Après avoir récupérer la donnée de la table gTb_Rot_Min[Ar_ID_Lua], il ne reste plus qu'à rappeler notre fonction fn_Operation avec cette fois-ci comme cinquième argument, la valeur de la constante C.FERMETURE. Cet argument a été utilisé dans la fonction M.Ferme_ID25(Ar_ID_Lua) à la ligne n° 1091 :
function fn_Operation(Ar_ID_Lua, Ar_Rot_Souhait, Ar_Sens_Mouvement, Ar_Pos_Pt_Rot, Ar_Operation)
Le déroulé des tests conditionnels concernant les constantes étant strictement le même que l'opération pour l'ouverture, nous allons passer directement à la partie réservée pour l'opération de la fermeture à partir de la ligne n° 1823.
Ici la variable Nb_Rotation_Min_Max est égale à la valeur de la table gTb_Rot_Min[Ar_ID_Lua] et celle-ci est égale à 0° (ligne n° 1089 dans la fonction M.Ferme_ID25(Ar_ID_Lua).
elseif Ar_Operation == C.FERMETURE then
-- Par défaut, l'angle minimal pour la fermeture des modèles est égal à la valeur de la table gTb_Rot_Min[Ar_ID_Lua]
Nb_Rotation_Min_Max = gTb_Rot_Min[Ar_ID_Lua]
if Ar_Pos_Pt_Rot == C.ROT_X then
-- Tant que la valeur calculée de la rotation est inférieure ou égale à la valeur absolue de la rotation max... la boucle continue
while gTb_Rot_Calcul[Ar_ID_Lua] >= Nb_Rotation_Min_Max do
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] - gTb_Rot_Increment[Ar_ID_Lua]
-- Applique la rotation X au modèle
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_Calcul[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Z[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
-- Bug lors de la dernière itération. Réinjecter la valeur gTb_Rot_Min[Ar_ID_Lua] de base pour la rotation concernée
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_Min[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Z[Ar_ID_Lua] )
elseif Ar_Pos_Pt_Rot == C.ROT_Y then
-- Idem que pour C.ROT_X (voir lignes 1856 et 1857)
while gTb_Rot_Calcul[Ar_ID_Lua] >= Nb_Rotation_Min_Max do
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] - gTb_Rot_Increment[Ar_ID_Lua]
-- Applique la rotation Y au modèle
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua], gTb_Rot_Z[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
-- Bug lors de la dernière itération. Réinjecter la valeur gTb_Rot_Min[Ar_ID_Lua] de base pour la rotation concernée
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Min[Ar_ID_Lua], gTb_Rot_Z[Ar_ID_Lua] )
elseif Ar_Pos_Pt_Rot == C.ROT_Z then
-- Idem que pour C.ROT_X (voir lignes 1856 et 1857)
while gTb_Rot_Calcul[Ar_ID_Lua] >= Nb_Rotation_Min_Max do
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Calcul[Ar_ID_Lua] - gTb_Rot_Increment[Ar_ID_Lua]
-- Applique la rotation Z au modèle
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Calcul[Ar_ID_Lua] )
-- Temporisation
fn_Tempo( C.TEMPO )
end
Comme nous avons demandé une rotation Z, les conditions retournées pour l'égalité des tests conditionnels aux lignes n° 1825 et 1841 sont fausses, ce qui nous amène tout naturellement à la ligne n° 1857. Ici, l'égalité est vraie car Ar_Pos_Pt_Rot est = à C.ROT_Z. Le bloc de code situé entre les lignes n° 1860 et 1868 va pouvoir être exécuté. Rentrons dans les détails :
A la ligne n° 1860, notre boucle while démarre avec la condition suivante : Tant que la valeur dans la table gTb_Rot_Calcul[Ar_ID_Lua] est supérieure ou égale ( >= ) à Nb_Rotation_Min_Max alors le cycle de la boucle va continuer.
Pour rappel, notre table gTb_Rot_Calcul[Ar_ID_Lua] a déjà mémorisé la valeur angulaire calculée précédemment dans l’opération consacrée à l'ouverture et qui est actuellement de 88° (ce qui évite, ceci dit, d'utiliser la fonction EEPStructureGetRotation(.....) pour récupérer la valeur de l'angle). Comme la valeur de la table gTb_Rot_Min[Ar_ID_Lua] est égale à 0, la ligne n° 1862 va soustraire la valeur de l'incrément à la table gTb_Rot_Calcul[Ar_ID_Lua]. Notre angle va ainsi diminuer progressivement à chaque itération de la boucle.
Le reste se passe de commentaire, tant que la valeur dans la table gTb_Rot_Calcul[Ar_ID_Lua] est supérieure ou égale ( >= ) à Nb_Rotation_Min_Max, la ligne n° 1864 applique le nouvel angle à la rotation Z par l'intermédiaire de la fonction EEPStructureSetRotation(...). Ensuite la fonction fn_Tempo(C.TEMPO) intervient et la boucle recommence jusqu'à ce que gTb_Rot_Calcul[Ar_ID_Lua] soit = à 0.
Une fois la boucle terminée, l'exécution du programme se poursuit après la ligne 1868 dont voici le code ci-dessous :
-- Bug lors de la dernière itération. Réinjecter la valeur gTb_Rot_Min[Ar_ID_Lua] de base pour la rotation concernée
EEPStructureSetRotation( Ar_ID_Lua, gTb_Rot_X[Ar_ID_Lua], gTb_Rot_Y[Ar_ID_Lua], gTb_Rot_Min[Ar_ID_Lua] )
end
-- Faire correspondre dans la table gTb_Rot_Calcul la valeur assignée dans Tb_Rot_Min[Ar_ID_Lua] (synchronisation)
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Min[Ar_ID_Lua]
end
Indépendamment de notre volonté, nous devons corriger un léger "bug" inhérent à EEP car la vitesse de la boucle dépasse de loin la mise à jour en interne des données en raison de 5 fois par seconde. Ce mécanisme propre à EEP est gérée par la fonction EEPMain(). Ce qui veut dire que les données sont mises à jour uniquement toutes les 200 millisecondes.
A l'échelle de l'exécution du programme, ce "court" laps de temps de 200 millisecondes devient une éternité proportionnellement à la vitesse d'exécution du processeur !
Ainsi lors de la dernière itération de la boucle, la fonction EEPStructureSetRotation(...) n'a pas le temps nécessaire pour appliquer la valeur correcte. Il en résulte un calcul sous la forme d'un nombre scientifique totalement erroné différent de la valeur angulaire minimale demandée.
Voilà pourquoi la ligne n° 1871 ordonne à nouveau une rotation en appliquant directement la valeur de gTb_Rot_Min[Ar_ID_Lua]. Ainsi nous sommes sûr et certain d'avoir affecté la valeur angulaire minimale correcte lorsque la fonction va se terminer.
Et pour finir, la ligne n° 1876 synchronise la valeur de la table gTb_Rot_Calcul[Ar_ID_Lua] avec gTb_Rot_Min[Ar_ID_Lua]. Si cette synchronisation était omise, lors de la prochaine ouverture du même modèle, le premier calcul pour l'incrémentation engendrerai une erreur car gTb_Rot_Calcul[Ar_ID_Lua] contiendra à coup sûr le dernier calcul erroné. Nous allons le confirmer dans la vidéo ci-dessous.
Maintenant notre "mur-porte" devrait se refermer ! Exemple ci-dessous mais cette fois-ci, nous allons visualiser dans la fenêtre d'évènements le calcul de l'angle dans la boucle :
Cliquez sur le bandeau ci-dessous pour afficher le fichier journal de la fenêtre d'évènements pour cette vidéo :
Fichier journal de la fenêtre d'évènements
gTb_Rot_Calcul[Ar_ID_Lua] : 88°
gTb_Rot_Calcul[Ar_ID_Lua] = 87.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 87.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 87.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 87.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 87°
gTb_Rot_Calcul[Ar_ID_Lua] = 86.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 86.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 86.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 86.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 86°
gTb_Rot_Calcul[Ar_ID_Lua] = 85.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 85.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 85.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 85.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 85°
gTb_Rot_Calcul[Ar_ID_Lua] = 84.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 84.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 84.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 84.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 84°
gTb_Rot_Calcul[Ar_ID_Lua] = 83.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 83.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 83.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 83.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 83°
gTb_Rot_Calcul[Ar_ID_Lua] = 82.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 82.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 82.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 82.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 82°
gTb_Rot_Calcul[Ar_ID_Lua] = 81.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 81.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 81.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 81.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 81°
gTb_Rot_Calcul[Ar_ID_Lua] = 80.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 80.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 80.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 80.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 80°
gTb_Rot_Calcul[Ar_ID_Lua] = 79.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 79.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 79.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 79.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 79°
gTb_Rot_Calcul[Ar_ID_Lua] = 78.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 78.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 78.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 78.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 78°
gTb_Rot_Calcul[Ar_ID_Lua] = 77.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 77.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 77.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 77.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 77°
gTb_Rot_Calcul[Ar_ID_Lua] = 76.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 76.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 76.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 76.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 76°
gTb_Rot_Calcul[Ar_ID_Lua] = 75.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 75.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 75.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 75.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 75°
gTb_Rot_Calcul[Ar_ID_Lua] = 74.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 74.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 74.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 74.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 74°
gTb_Rot_Calcul[Ar_ID_Lua] = 73.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 73.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 73.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 73.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 73°
gTb_Rot_Calcul[Ar_ID_Lua] = 72.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 72.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 72.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 72.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 72°
gTb_Rot_Calcul[Ar_ID_Lua] = 71.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 71.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 71.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 71.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 71°
gTb_Rot_Calcul[Ar_ID_Lua] = 70.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 70.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 70.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 70.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 70°
gTb_Rot_Calcul[Ar_ID_Lua] = 69.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 69.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 69.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 69.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 69°
gTb_Rot_Calcul[Ar_ID_Lua] = 68.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 68.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 68.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 68.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 68°
gTb_Rot_Calcul[Ar_ID_Lua] = 67.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 67.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 67.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 67.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 67°
gTb_Rot_Calcul[Ar_ID_Lua] = 66.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 66.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 66.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 66.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 66°
gTb_Rot_Calcul[Ar_ID_Lua] = 65.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 65.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 65.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 65.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 65°
gTb_Rot_Calcul[Ar_ID_Lua] = 64.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 64.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 64.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 64.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 64°
gTb_Rot_Calcul[Ar_ID_Lua] = 63.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 63.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 63.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 63.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 63°
gTb_Rot_Calcul[Ar_ID_Lua] = 62.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 62.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 62.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 62.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 62°
gTb_Rot_Calcul[Ar_ID_Lua] = 61.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 61.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 61.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 61.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 61°
gTb_Rot_Calcul[Ar_ID_Lua] = 60.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 60.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 60.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 60.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 60°
gTb_Rot_Calcul[Ar_ID_Lua] = 59.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 59.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 59.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 59.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 59°
gTb_Rot_Calcul[Ar_ID_Lua] = 58.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 58.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 58.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 58.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 58°
gTb_Rot_Calcul[Ar_ID_Lua] = 57.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 57.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 57.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 57.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 57°
gTb_Rot_Calcul[Ar_ID_Lua] = 56.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 56.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 56.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 56.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 56°
gTb_Rot_Calcul[Ar_ID_Lua] = 55.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 55.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 55.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 55.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 55°
gTb_Rot_Calcul[Ar_ID_Lua] = 54.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 54.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 54.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 54.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 54°
gTb_Rot_Calcul[Ar_ID_Lua] = 53.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 53.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 53.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 53.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 53°
gTb_Rot_Calcul[Ar_ID_Lua] = 52.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 52.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 52.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 52.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 52°
gTb_Rot_Calcul[Ar_ID_Lua] = 51.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 51.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 51.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 51.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 51°
gTb_Rot_Calcul[Ar_ID_Lua] = 50.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 50.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 50.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 50.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 50°
gTb_Rot_Calcul[Ar_ID_Lua] = 49.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 49.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 49.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 49.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 49°
gTb_Rot_Calcul[Ar_ID_Lua] = 48.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 48.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 48.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 48.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 48°
gTb_Rot_Calcul[Ar_ID_Lua] = 47.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 47.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 47.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 47.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 47°
gTb_Rot_Calcul[Ar_ID_Lua] = 46.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 46.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 46.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 46.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 46°
gTb_Rot_Calcul[Ar_ID_Lua] = 45.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 45.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 45.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 45.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 45°
gTb_Rot_Calcul[Ar_ID_Lua] = 44.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 44.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 44.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 44.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 44°
gTb_Rot_Calcul[Ar_ID_Lua] = 43.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 43.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 43.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 43.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 43°
gTb_Rot_Calcul[Ar_ID_Lua] = 42.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 42.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 42.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 42.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 42°
gTb_Rot_Calcul[Ar_ID_Lua] = 41.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 41.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 41.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 41.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 41°
gTb_Rot_Calcul[Ar_ID_Lua] = 40.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 40.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 40.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 40.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 40°
gTb_Rot_Calcul[Ar_ID_Lua] = 39.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 39.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 39.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 39.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 39°
gTb_Rot_Calcul[Ar_ID_Lua] = 38.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 38.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 38.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 38.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 38°
gTb_Rot_Calcul[Ar_ID_Lua] = 37.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 37.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 37.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 37.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 37°
gTb_Rot_Calcul[Ar_ID_Lua] = 36.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 36.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 36.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 36.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 36°
gTb_Rot_Calcul[Ar_ID_Lua] = 35.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 35.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 35.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 35.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 35°
gTb_Rot_Calcul[Ar_ID_Lua] = 34.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 34.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 34.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 34.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 34°
gTb_Rot_Calcul[Ar_ID_Lua] = 33.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 33.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 33.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 33.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 33°
gTb_Rot_Calcul[Ar_ID_Lua] = 32.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 32.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 32.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 32.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 32°
gTb_Rot_Calcul[Ar_ID_Lua] = 31.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 31.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 31.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 31.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 31°
gTb_Rot_Calcul[Ar_ID_Lua] = 30.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 30.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 30.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 30.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 30°
gTb_Rot_Calcul[Ar_ID_Lua] = 29.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 29.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 29.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 29.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 29°
gTb_Rot_Calcul[Ar_ID_Lua] = 28.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 28.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 28.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 28.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 28°
gTb_Rot_Calcul[Ar_ID_Lua] = 27.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 27.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 27.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 27.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 27°
gTb_Rot_Calcul[Ar_ID_Lua] = 26.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 26.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 26.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 26.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 26°
gTb_Rot_Calcul[Ar_ID_Lua] = 25.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 25.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 25.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 25.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 25°
gTb_Rot_Calcul[Ar_ID_Lua] = 24.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 24.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 24.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 24.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 24°
gTb_Rot_Calcul[Ar_ID_Lua] = 23.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 23.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 23.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 23.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 23°
gTb_Rot_Calcul[Ar_ID_Lua] = 22.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 22.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 22.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 22.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 22°
gTb_Rot_Calcul[Ar_ID_Lua] = 21.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 21.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 21.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 21.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 21°
gTb_Rot_Calcul[Ar_ID_Lua] = 20.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 20.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 20.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 20.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 20°
gTb_Rot_Calcul[Ar_ID_Lua] = 19.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 19.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 19.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 19.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 19°
gTb_Rot_Calcul[Ar_ID_Lua] = 18.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 18.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 18.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 18.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 18°
gTb_Rot_Calcul[Ar_ID_Lua] = 17.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 17.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 17.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 17.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 17°
gTb_Rot_Calcul[Ar_ID_Lua] = 16.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 16.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 16.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 16.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 16°
gTb_Rot_Calcul[Ar_ID_Lua] = 15.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 15.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 15.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 15.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 15°
gTb_Rot_Calcul[Ar_ID_Lua] = 14.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 14.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 14.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 14.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 14°
gTb_Rot_Calcul[Ar_ID_Lua] = 13.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 13.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 13.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 13.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 13°
gTb_Rot_Calcul[Ar_ID_Lua] = 12.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 12.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 12.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 12.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 12°
gTb_Rot_Calcul[Ar_ID_Lua] = 11.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 11.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 11.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 11.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 11°
gTb_Rot_Calcul[Ar_ID_Lua] = 10.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 10.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 10.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 10.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 10°
gTb_Rot_Calcul[Ar_ID_Lua] = 9.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 9.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 9.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 9.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 9°
gTb_Rot_Calcul[Ar_ID_Lua] = 8.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 8.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 8.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 8.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 8°
gTb_Rot_Calcul[Ar_ID_Lua] = 7.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 7.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 7.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 7.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 7°
gTb_Rot_Calcul[Ar_ID_Lua] = 6.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 6.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 6.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 6.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 6°
gTb_Rot_Calcul[Ar_ID_Lua] = 5.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 5.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 5.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 5.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 5°
gTb_Rot_Calcul[Ar_ID_Lua] = 4.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 4.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 4.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 4.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 4°
gTb_Rot_Calcul[Ar_ID_Lua] = 3.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 3.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 3.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 3.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 3°
gTb_Rot_Calcul[Ar_ID_Lua] = 2.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 2.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 2.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 2.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 2°
gTb_Rot_Calcul[Ar_ID_Lua] = 1.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 1.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 1.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 1.2°
gTb_Rot_Calcul[Ar_ID_Lua] = 1°
gTb_Rot_Calcul[Ar_ID_Lua] = 0.8°
gTb_Rot_Calcul[Ar_ID_Lua] = 0.6°
gTb_Rot_Calcul[Ar_ID_Lua] = 0.4°
gTb_Rot_Calcul[Ar_ID_Lua] = 0.2°
gTb_Rot_Calcul[Ar_ID_Lua] = -1.27676e-15°
(Bug corrigé)
gTb_Rot_Calcul[Ar_ID_Lua] = gTb_Rot_Min[Ar_ID_Lua] -> 0°
Comme vous pouvez le constater, la dernière itération de la boucle retourne un résultat totalement erroné mais bien-sûr corrigé avec la valeur de gTb_Rot_Min[Ar_ID_Lua]. Il nous reste à vérifier en image les propriétés de l'objet :
Nous constatons effectivement l'angle de rotation Z fixé à 0°. Le n° ID du modèle entouré en rouge est toujours le n° 25.
Conclusion
Dans cet article nous avons découvert comment appliquer une rotation de base à un objet bien qu'une simple boucle numérique avec 5 lignes de code ferait l'affaire.
Mais vous commencerez à réaliser dans le prochain article pourquoi j'ai voulu vous présenter les différents fichiers du script, montrer les appels entre les différentes fonctions, etc... Nous avons commencé à préparer le terrain pour aller plus loin et ajouter aux modèles des capacités d'animation qui de base n'existent pas.
J'ai conscience que tout ceci n'est pas facile à appréhender mais le résultat en vaut la peine. Si vous voulez apporter du dynamisme dans vos réseaux, rajouter de l'animation est un réel atout pour lequel il ne faut pas se priver ! Et c'est bien l'intention qui nous anime ici.
Et pour vous donner un avant-goût du prochain article, une petite vidéo avec notre "mur-porte" mais qui s'ouvre cette fois-ci à partir de la droite (alors que la rotation de base est à gauche) :
Un nouveau point de rotation a été ajouté "virtuellement" au modèle pour l'ouverture à droite. Comment ? réponse dans le prochain article...
Et je précise que la caméra n'a pas été tournée de l'autre sens pour inverser les côtés du modèle car elle est fixe ! 😉
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 !