Utilisation du moteur 3d ogre, du moteur physique newton et des scènes ofusion

Description

Bonjour

Je vous propose un outil implémentant OGRE et NEWTON sous VB 2008. Pour ce faire, j'ai utilisé les wrappers correspondants Mogre et MogreNewt.

C'est un outil permettant de visualiser une scène exportée depuis 3DS Max avec l'outil oFusion. Il présente sous une interface simple, une fenêtre de rendu 3D incorporée à une forme (Embeded Ogre Window), la gestion des collisions de la caméra (FPS like) ou pas, la possibilité de sélecionner des objets et de jouer avec, de modifier les lumières, leurs couleurs, de jouer avec les ombres...

C'est un premier projet que j'ai réalisé pour prendre en main tous ses outils. Il n'est pas encore parfait, mais est quand même bien avancé, et toutes les fonctions de bases semblent fonctionner (en tout cas chez moi).

Je le met à votre disposition, car je n'ai trouvé aucun exemple en VB quant à l'utilisation de OGRE et Newton, ce qui est bien dommage car se sont vraiment des outils puissants.

J'inclut une scène exemple, pour faire mumuse avec.

Bien sur, si vous voulez aller plus loin, il vous faudra les différents SDK, on peut les trouver sur www.ogre3D.org, http://www.newtondynamics.com/, les wiki : http://www.ogre3d.org/wiki/index.php/Main_Page, http://www.ogre3d.org/wiki/index.php/Mogre_Tutorials...et bien sur, google est mon ami ;o)

Alors voilà, dites moi ce que vous en pensez.

Edit : je viens de commencer à transposer le code dans une classe pour pouvoir le réutiliser de manière simple, j'ai réimplémenté toute la partie chargement affichage et déplacement, il ne reste plus qu'à réimplémenter la gestion de la physique. Quand elle sera terminée, je la déposerais ici, ce qui permettra d'utiliser un moteur 3D puissant en .Net de manière simplissime. (bon, il faudra avoir 3DS MAx quand même...sinon, il faudra voir pour adapter la partie chargement avec les fichiers générés par l'exporter de blender).

Edit 2 : comme promis, la classe est online : http://www.vbfrance.com/codes/CLASSE-UTILISATION-OGRE-NEWTON_47581.aspx

Source / Exemple :


Imports Mogre
Imports MOIS
Imports MogreNewt
Imports OFusion
Imports System.Runtime.InteropServices

Public Class frmMain

#Region "Déclarations"

    Private myRoot As Root 'La racine OGRE
    Private mySceneManager As SceneManager 'Le SceneManager OGRE
    Private myCamera As Camera 'La caméra
    Private MyWindow As RenderWindow 'La fenêtre de rendu (ici ce sera une picturebox)

    Private init As Boolean = True 'Permettra de savoir si on est en cours d'initialisation de l'application
    Private EnCoursDeSelection = False 'Permettra de savoir si un objet est en cours de sélection
    Private EnCoursDeTranslation As Boolean = False 'Permettra de savoir si un objet est en cours de translation
    Private ShowAxes As Boolean = False 'Axe du monde visible ou pas
    Private TrackObjet As Boolean = False 'Autotracking de la caméra actif ou pas
    Private RenduEnCours As Boolean = True 'Sert à savoir si on à quitter l'appli pour stopper le rendu

    Private Scene As OSMScene 'La variable pour le chargement de la scene oFusion
    Private MyShadowTechnique As Mogre.ShadowTechnique 'Continedra le choix du type d'affichage des ombres

    'Variables pour la sélectiond dans la TreeView
    Private SceneNodeSelected As Mogre.SceneNode 'Contiendra le noeud sélectionné dans le treeview
    Private MObjectSelected As Mogre.MovableObject 'Contiendra l'objet sélectionnée
    Private LightSelected As Mogre.Light 'Contiendra la lumière sélectionnée

    Private NodeAxe As Mogre.SceneNode 'L'axe des objets
    Private NodeAxeMonde As Mogre.SceneNode 'L'axe du monde

    'Déclarations pour Newton
    Private myWorld As World = Nothing 'Le monde physique
    Private Gravity As Single = -9.8 'Et oui, on aura un poid...(Sur terre = 9.81m/s²

    'La taille du body représentant le corp physique de la caméra
    'Ajuster cette valeur en fonction de la scène. A priori, 70 représente un humain d'à peu près 1m80, si mes 
    'estimations sont juste.
    Private CamSize As Mogre.Vector3 = New Mogre.Vector3(10, 70, 10)
    Private CamNode As SceneNode 'Le node de la caméra
    Private CamViewNode As SceneNode
    Private CamBody As Body 'Le corps physique de la caméra
    Private CamColl As CollisionPrimitives.Ellipsoid 'Le type de collision
    Private NoMovement As Boolean = True
    Private camera_rotation_x As Single
    Private camera_rotation_xx As Single
    Private camera_rotation_y As Mogre.Degree
    Private y_rotation_cont As Degree
    Private y_limit_a As Single = 90
    Private y_limit_b As Single = -90
    Private InitialCamBodyPos As Mogre.Vector3
    Private InitialCamBodyOrient As Mogre.Quaternion

    'Déclaration pour MOIS
    Private inputManager As MOIS.InputManager
    Private inputKeyboard As MOIS.Keyboard = Nothing
    Private inputMouse As MOIS.Mouse = Nothing

    'Translate & rotate scalars
    Private moving As Boolean

#End Region

#Region "CONSTANTES"

    'Changer ces constantes pour avoir des vitesses de déplacement et/ou de rotation différentes (mode détection de collisions inactif)
    Const TRANSLATE As Single = 600
    Const ROTATE As Single = 1

#End Region

#Region "Procédures & Fonctions"

    Private Function get_body_position(ByVal bod As Body) As Mogre.Vector3 'retourne la position du body 
        Dim orient As Quaternion
        Dim pos As Mogre.Vector3

        bod.GetPositionOrientation(pos, orient)

        Return pos

    End Function

    Private Function get_body_orientation(ByVal bod As Body) As Quaternion  ' retourne l'orientation du body
        Dim orient As Quaternion
        Dim pos As Mogre.Vector3

        bod.GetPositionOrientation(pos, orient)

        Return orient

    End Function

    Private Sub camera_force_callback(ByVal Corps As Body)

        'Cette procédure est appelée à chaque update du monde physique.
        'On y applique la gravité ainsi que la rotation sur l'axe des X

        If mnuGravite.Checked Then
            Dim masse As Single
            Dim inertie As Mogre.Vector3
            Dim Force As Mogre.Vector3

            'on récupère la position et l'orientation du corp passé en paramètre
            Dim posBody As Mogre.Vector3 = CamBody.Position
            Dim OrientBody As Quaternion = CamBody.Orientation
            Corps.GetMassMatrix(masse, inertie) 'on récupère sa masse et son inertie
            Force = New Mogre.Vector3(0, Gravity * 2000, 0) 'on y applique la gravité, le facteur de multiplication est là pour ajusté la valeur.
            'Curieusement, si je ne multiplie pas, on se croirait sur la Lune. Si quelqu'un sait pourquoi, merci de me le dire.
            Force *= masse
            Corps.AddForce(Force) 'on applique la force de gravité

        End If
        Corps.Omega = New Mogre.Vector3(0, camera_rotation_x, 0) 'pour le déplacement sur X

    End Sub

    Private Sub InitOgre()
        'Création de la racine OGRE et du SceneManager. Appeler dans le load de la form, le menu Ouvrir et le menu recharger

        myRoot = New Root("Plugins.cfg", "ogre.cfg", "ogre.log")
        mySceneManager = myRoot.CreateSceneManager(SceneType.ST_GENERIC)

    End Sub

    Private Sub InitTree(ByVal SceneNode As Mogre.SceneNode, ByVal sceneTreeNode As TreeNode)

        'Remplissage de la TreeView de sélection des objets
        'c'est une fonction récursive
        'TODO : classement en ordre alphabétique dans les 3 noeuds principaux

        Dim NumChildNodes As UShort 'le nombre d'enfants du scenenode passé en paramètre
        Dim NewNode As TreeNode = New TreeNode 'on crèe un nouveau node dans le treeview qui va recevoir les paramètres à ajouter au treeview

        Try
            NumChildNodes = SceneNode.NumChildren 'on récupère le nombre d'enfants du scenenode
            NewNode.Name = SceneNode.Name 'Son nom
            NewNode.Text = SceneNode.Name + " (" + NumChildNodes.ToString + " enfants)" 'on concatène le nombre d'enfants au nom pour l'affichage dans le treeview
            NewNode.Tag = SceneNode 'on met le scenenode dans le tag du noeud de la treeview pour s'en servir lors de la sélection du noeud dans la treeview (cf Treeview_AfterSelect)

            If SceneNode.Name.ToUpper.Contains("AXE") Then 'si on est sur un scenenode axe, on sort de la procédure car on ne veut pas le faire appraitre dans la treeview
                Exit Sub
            End If
            If SceneNode.Name.ToUpper.Contains("ROOT") Then 'si on est sur root du scenemanager, on ne fait rien, on se contante de lancer le premier niveau de récursivité
                treeScene.Nodes.Item(0).Text = "Root (" + NumChildNodes.ToString + " enfants)"
                For i As UShort = 0 To NumChildNodes - 1
                    InitTree(SceneNode.GetChild(i), treeScene.SelectedNode)
                Next
            Else 'sinon, on teste le type d'objet et suvant le cas on l'ajoute au noeud du treeview adéquate
                If SceneNode.GetAttachedObject(SceneNode.Name).MovableType = "Camera" Then
                    'Cette ligne permet de sélectionner le bon neoud auquel ajouté l'objet
                    treeScene.SelectedNode = treeScene.Nodes.Item(0).Nodes.Item(1)
                ElseIf SceneNode.GetAttachedObject(SceneNode.Name).MovableType = "Light" Then
                    treeScene.SelectedNode = treeScene.Nodes.Item(0).Nodes.Item(3)
                ElseIf SceneNode.GetAttachedObject(SceneNode.Name).MovableType = "Entity" Then
                    treeScene.SelectedNode = treeScene.Nodes.Item(0).Nodes.Item(2)
                End If

                'on ajoute le noeud
                treeScene.SelectedNode.Nodes.Add(NewNode)

                'treeScene.SelectedNode = treeScene.Nodes(treeScene.Nodes.Count - 1)

                If NumChildNodes > 0 Then 'si il y a des nodes enfants, on desend dans le scenenode
                    For i As UShort = 0 To NumChildNodes - 1
                        InitTree(SceneNode.GetChild(i), treeScene.SelectedNode)
                    Next
                End If

            End If
        Catch ex As System.Runtime.InteropServices.SEHException
            'en cas d'erreur....on ne fait rien, sinon, des messages apparaissent lors de la création du treeview, on se contente d'un gestionnaire générique pour ne pas planter le programme
            treeScene.SelectedNode = treeScene.Nodes.Item(0).Nodes.Item(0)
            'on ajoute le noeud
            treeScene.SelectedNode.Nodes.Add(NewNode)
            If NumChildNodes > 0 Then 'si il y a des nodes enfants, on desend dans le scenenode
                For i As UShort = 0 To NumChildNodes - 1
                    InitTree(SceneNode.GetChild(i), treeScene.SelectedNode)
                Next
            End If
        End Try

    End Sub

    Private Sub InitScene()
        Try
            'On initialise la racine OGRE
            myRoot.Initialise(False)

            'On crè la fenêtre, ici ce sera dans le contrôle picMogre, une picturebox
            Dim misc As NameValuePairList = New NameValuePairList
            misc("externalWindowHandle") = picMogre.Handle.ToString
            Dim const_list As Const_NameValuePairList = misc.ReadOnlyInstance
            MyWindow = myRoot.CreateRenderWindow("OgreWieport", 0, 0, False, const_list)

            'On ajoute un handler qui permettre de récupérer la variable timeSinceLastFrame pour la gestion des déplacements
            'AddHandler myRoot.FrameStarted, AddressOf FrameStarted

            'On crés le ressource manager avec les ressources choisie dans la boîte de chargement.
            'Par défaut, on prend le chemin de la scène
            ResourceGroupManager.Singleton.AddResourceLocation(System.IO.Path.GetDirectoryName(txtNomScene.Text), "FileSystem", "General")

            'On ajoute le répertoires OBJETS de l'application qui contient, entre autre, les axes
            ResourceGroupManager.Singleton.AddResourceLocation(Application.StartupPath + "\Objets", "FileSystem", "General")

            'et on parcourt la listbox de la fenêtre de choix pour ajouter les ressources supplémentaires (répertoires ou fichiers zip)
            If lstRessources.Items.Count > 0 Then
                For i As Integer = 0 To lstRessources.Items.Count - 1
                    'lstTypeRessources contient le type de ressource de la ressource considérée dans lstRessources
                    ResourceGroupManager.Singleton.AddResourceLocation(lstRessources.Items(i), lstTypeRessources.Items(i), "General")
                Next
            End If
            'Fini, on initialise toutes les ressources
            ResourceGroupManager.Singleton.InitialiseAllResourceGroups()

            'On charge la scène OSM grâce à la dll oFusion
            Scene = New OSMScene(mySceneManager, MyWindow)
            Scene.Initialize(txtNomScene.Text)
            Scene.CreateScene(mySceneManager.RootSceneNode)

            'création de la caméra
            If Scene.CameraList.Count > 0 Then
                'Si la scene contient au moins une caméra, on sélectionne la première
                myCamera = Scene.CameraList(0)
            Else
                'sinon, on en crè une par défaut
                myCamera = Scene.SceneMgr.CreateCamera("CameraDefaut")
                myCamera.SetPosition(0, 0, 0)
                myCamera.SetDirection(0, 0, 0)
                myCamera.FOVy = CType(1, Mogre.Radian)
                Dim camNode As SceneNode = mySceneManager.CreateSceneNode("CameraDefaut")
                camNode.AttachObject(myCamera)

                Dim CameraTarget As Mogre.SceneNode = mySceneManager.CreateSceneNode("CameraDefaut.target")
                CameraTarget.SetPosition(1000, 1000, 1000)
                CameraTarget.SetDirection(0, 0, 0)
                CameraTarget.SetScale(1, 1, 1)

                'On crè le viewport
                Dim ViewPort As Mogre.Viewport = MyWindow.AddViewport(myCamera)
                myCamera.AspectRatio = ViewPort.ActualWidth / ViewPort.ActualHeight
            End If
            txtFOV.Text = myCamera.FOVy.ValueRadians
            myCamera.NearClipDistance = 5

            InitialCamBodyOrient = myCamera.Orientation
            InitialCamBodyPos = myCamera.Position

            'si on a décidé d'activer la gestion des collisions, on initialise Newton
            If mnuDetectionDeCollision.Checked Then
                InitNewton()
            End If

            'initialisation de l'inputmanager MOIS
            InitInputHandler()

        Catch ex As System.Runtime.InteropServices.SEHException
            'en cas d'erreur....
            If OgreException.IsThrown Then
                MsgBox(OgreException.LastException.FullDescription, MsgBoxStyle.Critical, _
                     "Exeption OGRE!")
            Else
                MsgBox(ex.ToString, "Erreur")
            End If
        End Try
    End Sub

    Private Sub InitNewton()

        'Initialisation de Newton pour la gestion des collision caméras, je n'y suis pas arrivé avec les RayScenQuery...
        If myWorld Is Nothing Then
            myWorld = New World 'le monde physique
            'et initialisation de sa taille, par défaut elle est de 100 sur chaque axes.
            myWorld.SetWorldSize(New Mogre.Vector3(-100000, -100000, -100000), New Mogre.Vector3(100000, 100000, 100000))
        End If

        Dim cam_mass As Single = 90 'la masse de la caméra

        'Création du monde physique pour Newton

        Dim stat_col As MogreNewt.CollisionPrimitives.TreeCollisionSceneParser = New MogreNewt.CollisionPrimitives.TreeCollisionSceneParser(myWorld)
        stat_col.ParseScene(mySceneManager.RootSceneNode, False) 'Utilisation du parser de scène Newton, attention, à ce jour (juillet 2008), le parmètre true, au mieux, ne fonctionne pas et au pire plante le programme.

        'Création et paramétrage du body pour le monde
        Dim bod As MogreNewt.Body = New MogreNewt.Body(myWorld, stat_col)
        stat_col.Dispose()
        bod.AttachToNode(mySceneManager.RootSceneNode)
        bod.SetPositionOrientation(New Mogre.Vector3(0.0, 0.0, 0.0), Quaternion.IDENTITY)

        'Collision de la caméra
        Dim PosCam As Mogre.Vector3 = mySceneManager.GetSceneNode(myCamera.Name).Position 'on récupère la position de la caméra
        Dim OrientCam As Quaternion = mySceneManager.GetSceneNode(myCamera.Name).Orientation 'on récupère l'orientation de la caméra

        CamNode = mySceneManager.RootSceneNode.CreateChildSceneNode("CamNode") 'on crè un Scennode
        Me.CamNode.SetScale(Me.CamSize) 'on le met à la taille de la caméra
        Me.CamColl = New CollisionPrimitives.Ellipsoid(myWorld, Me.CamSize) 'on crè le système de collision
        Me.CamBody = New Body(myWorld, Me.CamColl) 'on l'applique au body représentant la caméra
        Me.CamColl.Dispose() 'on libère la collision
        Me.CamBody.AttachToNode(Me.CamNode) 'et on l'attache au noeud créé précédement

        'calcul de l'inertie de la caméra
        Dim cam_inertia As Mogre.Vector3 = MomentOfInertia.CalcEllipsoidSolid(cam_mass, Me.CamSize)
        Me.CamBody.SetMassMatrix(cam_mass, cam_inertia)
        AddHandler Me.CamBody.ForceCallback, AddressOf camera_force_callback 'la fonction appelée lors de l'update du monde physique
        Me.CamBody.AutoFreeze = False 'Important, sinon, iompossible de déplacer le body...

        'Création d'un "UP Vector" pour empécher le body de tourner sur Y
        Dim uv2 As BasicJoints.UpVector = New BasicJoints.UpVector(myWorld, Me.CamBody, Mogre.Vector3.UNIT_Y)

        'Création d'un scenenode 1 unités au dessus du camnode pour y mettre la caméra
        'Ca nécessite un peu d'explications :
        'Si on met 0 comme valeur sur Y, la caméra est positionnée au centre du body.
        'Comme le but est de représenter un être humain, ça ne le fait pas.
        'Par contre, la valeur 1 positionne la caméra juste au dessus du body, mais, à contrarion, dans une pièce
        'au plafond trop bas, la caméra va passer au dessus du plafond....Pour le moment, je n'ai pas trouvé mieux.

        Me.CamViewNode = Me.CamNode.CreateChildSceneNode("CamViewNode", New Mogre.Vector3(0, 1, 0)) 'création du scenenode
        mySceneManager.RootSceneNode.DetachObject(myCamera) 'on détache la caméra du rootscenenode (créé par OSMLoader)
        Me.CamViewNode.AttachObject(myCamera) 'et on attache la caméra au viewnode

        CamBody.SetPositionOrientation(PosCam, OrientCam) 'Sert à récupérer la position et orientation initiale de la caméra pour positionner son corp.
        InitialCamBodyOrient = get_body_orientation(CamBody)
        InitialCamBodyPos = get_body_position(CamBody)

        'CamBody.MaterialGroupID = New MogreNewt.MaterialID(myWorld) 

        MogreNewt.Debugger.Instance.Init(mySceneManager) 'et on initialise le debugger newton.

    End Sub

    Private Sub InitInputHandler()

        'Définition du mode d'accès de la souris et du handle de fenêtre à gérer. ATTENTION : ce n'est pas le handle du contrôle qui affichera le rendu mais celui de la fenêtre qui le contient

        Dim param As MOIS.ParamList = New MOIS.ParamList()

        'Les 4 premiers paramètres servent à signaler que MOIS ne sera pas le seul à être capable de gérer le clavier et la souris
        'ceci permet de disposer du curseur de la souris pour faire autre chose pendant que Ogre rend ses frames
        param.Insert("w32_mouse", "DISCL_NONEXCLUSIVE")
        param.Insert("w32_mouse", "DISCL_FOREGROUND")
        param.Insert("w32_keyboard", "DISCL_NONEXCLUSIVE")
        param.Insert("w32_keyboard", "DISCL_FOREGROUND")
        param.Insert("WINDOW", Me.Handle.ToString) 'ici, on passe le handle de notre "top level window" (cf. DirectX et DirectInput)

        'Hook de l'input manager à la fenêtre
        inputManager = MOIS.InputManager.CreateInputSystem(param)
        If Not inputManager Is Nothing Then

            'Creation des device de capture MOIS
            Try
                'Le clavier
                inputKeyboard = CType(inputManager.CreateInputObject(MOIS.Type.OISKeyboard, False), MOIS.Keyboard)
            Catch ex As System.Runtime.InteropServices.SEHException
                'en cas d'erreur....
                If OISException.IsThrown Then
                    MsgBox(OISException.LastException.eText, MsgBoxStyle.Critical, _
                         "Exeption OIS!")
                Else
                    MsgBox(ex.ToString, "Erreur")
                End If
            End Try
            Try
                'La souris
                inputMouse = CType(inputManager.CreateInputObject(MOIS.Type.OISMouse, False), MOIS.Mouse)
            Catch ex As System.Runtime.InteropServices.SEHException
                'en cas d'erreur....
                If OISException.IsThrown Then
                    MsgBox(OISException.LastException.eText, MsgBoxStyle.Critical, _
                         "Exeption OIS!")
                Else
                    MsgBox(ex.ToString, "Erreur")
                End If
            End Try
            'A répéter pour d'autre input, joystick par exemple :
            '' ''Try
            '' ''    inputJoy = CType(inputManager.CreateInputObject(MOIS.Type.OISJoyStick, False), MOIS.JoyStick)
            '' ''Catch ex As System.Runtime.InteropServices.SEHException
            '' ''    'en cas d'erreur....
            '' ''    If OISException.IsThrown Then
            '' ''        MsgBox(OISException.LastException.eText, MsgBoxStyle.Critical, _
            '' ''             "Exeption OIS!")
            '' ''    Else
            '' ''        MsgBox(ex.ToString, "Erreur")
            '' ''    End If
            '' ''End Try
        End If

        ' Et l'évènement qui sera attaché au FrameStarted
        AddHandler myRoot.FrameStarted, AddressOf FrameStarted

    End Sub

    Private Sub DisposeOgre()
        'Pemet de nettoyer les instances OGRES en cas de rechargement de la scène ou de l'ouverture d'une nouvelle
        'en gros, on réinitialise toutes les instances Ogre

        SceneNodeSelected = Nothing
        MObjectSelected = Nothing

        myCamera = Nothing
        Scene = Nothing
        ResourceGroupManager.Singleton.DestroyResourceGroup("General")
        MyWindow.RemoveAllViewports()
        MyWindow.Dispose()
        myRoot.DestroySceneManager(mySceneManager)
        myRoot.DetachRenderTarget(MyWindow)
        myRoot.Dispose()
        MyWindow = Nothing
        mySceneManager = Nothing

    End Sub

    Private Sub DisposeNewton()

        'Permet de liberer les instances Newton, mais ça ne marche pas top, à paufiner....
        'Ainsi, si on recharge la scène ou si on en charge une autre, tout marche sauf que l'on a pas de mouvement sur X.
        'A l'heure qu'il est, je ne sais pas pourquoi

        RemoveHandler Me.CamBody.ForceCallback, AddressOf camera_force_callback
        myWorld.DestroyAllBodies()
        myWorld.Dispose()
        myWorld = Nothing

    End Sub

    Private Sub LoadAxe()
        'Permet de charger l'objet Axe et de l'ajouter à la scene

        Dim Axe01 As Entity 'Sera l'axe afficher sdur les objets
        Dim AxeMonde As Entity 'Comme son nom l'indique

        Try

            Axe01 = mySceneManager.CreateEntity("Axe01", "Axe.mesh") 'on charge l'objet
            AxeMonde = Axe01.Clone("AxeMonde") 'on le clone pour avoir deux axes séparés

            'On crè le node qui contioendra l'axe du monde
            NodeAxeMonde = mySceneManager.RootSceneNode.CreateChild("AxeMonde")
            NodeAxeMonde.AttachObject(AxeMonde)
            NodeAxeMonde.SetPosition(0, 0, 0)
            NodeAxeMonde.SetScale(1, 1, 1)
            NodeAxeMonde.SetVisible(False)
            NodeAxeMonde.ResetToInitialState()

            'on fait pareil pour l'axe des objets
            NodeAxe = mySceneManager.RootSceneNode.CreateChild("Axe01")
            NodeAxe.AttachObject(Axe01)
            NodeAxe.SetPosition(0, 0, 0)
            NodeAxe.SetScale(1, 1, 1)
            NodeAxe.InheritOrientation = False 'si on ne fait pas ça, l'axe se comporte étrangement lorsque l'objet tourne
            NodeAxe.InheritScale = False 'Pareil, le scale de l'objet s'ajoute au scale de l'axe
            NodeAxe.SetVisible(False)

        Catch ex As System.Runtime.InteropServices.SEHException
            'en cas d'erreur....
            If OgreException.IsThrown Then
                MsgBox(OgreException.LastException.FullDescription, MsgBoxStyle.Critical, _
                     "Exeption OGRE!")
            Else
                MsgBox(ex.ToString, "Erreur")
            End If

        End Try

    End Sub

    Public Function FrameStarted(ByVal evt As FrameEvent) As Boolean

        'Gère les évènements clavier et souris

        'le test pour annuler le mouvement de la caméra est mis en externe par rapport
        'au test de detection de collision car si on relache la souris avant les touches
        'la caméra continue à se déplacer, donc, on annule sa vélocité et sa rotation à la fin de la fonction

        Dim PasdeMouvement As Boolean = True

        If moving Then 'Si on a cliqué sur le bouton gauche de la souris, on se déplace dans le monde

            CaptureFunction() 'on lance la capture clavier/souris par MOIS

            'Il y a 2 systèmes de mouvement, avec ou sans détection de collisions
            If mnuDetectionDeCollision.Checked Then
                'Avec

                'Le principe est de récupérer l'orientation du corps de la caméra sur l'axe de déplacement choisi
                'et de lui affecter une vitesse de déplacement sur cette axe.
                'Lorsque l'on arrête le déplacement, on annule la vitesse.

                Dim direction As Mogre.Vector3

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_UP) Then
                    direction = get_body_orientation(CamBody) * Mogre.Vector3.NEGATIVE_UNIT_Z
                    CamBody.Velocity = CamBody.Velocity * New Mogre.Vector3(0, 1, 0) + direction * 200 * 2
                    PasdeMouvement = False
                End If

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_DOWN) Then
                    direction = get_body_orientation(CamBody) * Mogre.Vector3.UNIT_Z
                    CamBody.Velocity = CamBody.Velocity * New Mogre.Vector3(0, 1, 0) + direction * 200 * 2
                    PasdeMouvement = False
                End If

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_LEFT) Then
                    direction = get_body_orientation(CamBody) * Mogre.Vector3.NEGATIVE_UNIT_X
                    CamBody.Velocity = CamBody.Velocity * New Mogre.Vector3(0, 1, 0) + direction * 200 * 2
                    PasdeMouvement = False
                End If

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_RIGHT) Then
                    direction = get_body_orientation(CamBody) * Mogre.Vector3.UNIT_X
                    CamBody.Velocity = CamBody.Velocity * New Mogre.Vector3(0, 1, 0) + direction * 200 * 2
                    PasdeMouvement = False
                End If

                'Pour le déplacement sur Y (vertical) on se déplace sur le Y du monde et pas de la caméra
                'Donc, pas besoin de récupérer la direction de la caméra
                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_PGUP) Then
                    CamBody.Velocity = New Mogre.Vector3(0, 1000, 0)
                    PasdeMouvement = False
                End If

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_PGDOWN) Then
                    CamBody.Velocity = New Mogre.Vector3(0, -1000, 0)
                    PasdeMouvement = False
                End If

                'Pour la rotation de la caméra, on récupère le déplacement de la souris sur X et Y
                Dim mouseState As MOIS.MouseState_NativePtr = inputMouse.MouseState

                camera_rotation_x = -mouseState.X.rel * 0.5F
                If camera_rotation_x <> 0 Then
                    PasdeMouvement = False
                End If
                camera_rotation_y = -mouseState.Y.rel * 0.5F

                'On travaille sur le camviewnode pour que l'axe de la caméra soit orienté correctement
                CamViewNode.Pitch(camera_rotation_y)

                'Si on atteint un angle de rotation maximale vers le haut ou le bas, on annule la rotation
                y_rotation_cont += camera_rotation_y
                If y_rotation_cont > y_limit_a Or y_rotation_cont < y_limit_b Then
                    CamViewNode.Pitch(-camera_rotation_y)
                    y_rotation_cont -= camera_rotation_y
                End If

            Else
                'Sans

                'On applique simplement une translation ou une rotation à la caméra

                Dim myTranslation As Mogre.Vector3 = Mogre.Vector3.ZERO 'Pour le déplacement de la caméra sur ces axes sans détection de collision

                myTranslation.z = 0
                myTranslation.x = 0
                myTranslation.y = 0

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_UP) Then
                    myTranslation.z += -TRANSLATE
                End If

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_DOWN) Then
                    myTranslation.z += TRANSLATE
                End If

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_LEFT) Then
                    myTranslation.x += -TRANSLATE
                End If

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_RIGHT) Then
                    myTranslation.x += TRANSLATE
                End If

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_PGUP) Then
                    myTranslation.y += TRANSLATE
                End If

                If inputKeyboard.IsKeyDown(MOIS.KeyCode.KC_PGDOWN) Then
                    myTranslation.y += -TRANSLATE
                End If

                Dim mouseState As MOIS.MouseState_NativePtr = inputMouse.MouseState

                camera_rotation_x = mouseState.X.rel * -ROTATE
                camera_rotation_y = mouseState.Y.rel * -ROTATE

                'Et on déplace la caméra
                myCamera.Position += myCamera.Orientation * myTranslation * evt.timeSinceLastFrame
                myCamera.Yaw(camera_rotation_x * evt.timeSinceLastFrame)
                myCamera.Pitch(camera_rotation_y) 'si on multiplie par timesincelastframe, le résultat est plus qu'étrange...A voir.

            End If

        End If
        'C'est ici que l'on annule la vélocité et la rotation de la caméra.
        If PasdeMouvement And mnuDetectionDeCollision.Checked Then
            CamBody.Velocity = New Mogre.Vector3(0, 0, 0)
            camera_rotation_x = 0
        End If

        Return RenduEnCours 'tant que l'on ne quitte pas l'appli, on renvoi True.

    End Function

    Private Sub CaptureFunction()
        'Lance la capture du clavier et de la souris
        inputKeyboard.Capture()
        inputMouse.Capture()
    End Sub

    Private Sub RemplitGrille()

        'Permet de remplir la grille d'infos une fois qu'un objet est sélectionné

        Dim tab(1) As String
        Try

            'On efface le contenu de la grille  
            grdDetail.Rows.Clear()

            'Et on remplit
            'La position
            tab(0) = "Position X"
            tab(1) = SceneNodeSelected.Position.x.ToString
            grdDetail.Rows.Add(tab)
            tab(0) = "Position Y"
            tab(1) = SceneNodeSelected.Position.y.ToString
            grdDetail.Rows.Add(tab)
            tab(0) = "Position Z"
            tab(1) = SceneNodeSelected.Position.z.ToString
            grdDetail.Rows.Add(tab)

            'L'Orientation
            tab(0) = "Orientation X"
            tab(1) = SceneNodeSelected.Orientation.x.ToString
            grdDetail.Rows.Add(tab)
            tab(0) = "Orientation Y"
            tab(1) = SceneNodeSelected.Orientation.y.ToString
            grdDetail.Rows.Add(tab)
            tab(0) = "Orientation Z"
            tab(1) = SceneNodeSelected.Orientation.z.ToString
            grdDetail.Rows.Add(tab)
            tab(0) = "Orientation W"
            tab(1) = SceneNodeSelected.Orientation.w.ToString
            grdDetail.Rows.Add(tab)

            'L'échelle
            tab(0) = "Echelle X"
            tab(1) = SceneNodeSelected.GetScale.x.ToString
            grdDetail.Rows.Add(tab)
            tab(0) = "Echelle Y"
            tab(1) = SceneNodeSelected.GetScale.y.ToString
            grdDetail.Rows.Add(tab)
            tab(0) = "Echelle Z"
            tab(1) = SceneNodeSelected.GetScale.z.ToString
            grdDetail.Rows.Add(tab)

            'Les ombres
            tab(0) = "CastShadows"
            tab(1) = IIf(MObjectSelected.CastShadows, "Oui", "Non")
            grdDetail.Rows.Add(tab)

            'La visibilité
            tab(0) = "Visible"
            tab(1) = IIf(MObjectSelected.Visible, "Oui", "Non")
            grdDetail.Rows.Add(tab)

            'La Bounding box
            tab(0) = "ShowBoundingBox"
            tab(1) = IIf(SceneNodeSelected.ShowBoundingBox, "Oui", "Non")
            grdDetail.Rows.Add(tab)

            GetPosition()
            GetOrientation()
            GetEchelle()

        Catch ex As Exception

        End Try

    End Sub

    Private Sub GetOrientation()

        'Récupère l'orientation de l'objet sélectionné pour l'afficher dans les paramètres de le'objet
        Try
            txtOrientationX.Value = SceneNodeSelected.Orientation.x
            txtOrientationY.Value = SceneNodeSelected.Orientation.y
            txtOrientationZ.Value = SceneNodeSelected.Orientation.z
            txtOrientationW.Value = SceneNodeSelected.Orientation.w
        Catch ex As Exception

        End Try

    End Sub

    Private Sub GetPosition()

        'Récupère la position de l'objet sélectionné pour l'afficher dans les paramètres de le'objet
        Try
            txtPositionX.Value = SceneNodeSelected.Position.x
            txtPositionY.Value = SceneNodeSelected.Position.y
            txtPositionZ.Value = SceneNodeSelected.Position.z
        Catch ex As Exception

        End Try

    End Sub

    Private Sub GetEchelle()

        'Récupère l'échelle de l'objet sélectionné pour l'afficher dans les paramètres de le'objet
        txtScaleX.Value = SceneNodeSelected.GetScale.x
        txtScaleY.Value = SceneNodeSelected.GetScale.y
        txtScaleZ.Value = SceneNodeSelected.GetScale.z

    End Sub

#End Region

#Region "Evènements"

#Region "Form"

    Private Sub frmMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Me.Hide() 'Cache la fenêtre principale

        treeScene.Sort() 'trie la treeview, une fois triée, elle le reste même si on la recharge avec de nouvelles données.

        InitOgre()

        'Affichage de la fenêtre de choix de la scène à charger
        frmViewerload.ShowDialog()

        If frmViewerload.bValide Then
            'Si on a valider le choix.....
            'On affiche la form d'attente
            Cursor = Cursors.WaitCursor 'juste pour la beauté

            If frmViewerload.chkPhysique.Checked Then
                mnuDetectionDeCollision.Checked = True
                mnuGravite.Enabled = True
                mnuDebugActif.Enabled = True
            End If

            'Affichage de la fenêtre de paramètres OGRES, le cas échéant
            If frmViewerload.chkOgre.Checked Then
                If Not myRoot.ShowConfigDialog Then
                    Exit Sub
                End If
            Else
                If Not myRoot.RestoreConfig() Then
                    If Not myRoot.ShowConfigDialog Then
                        Exit Sub
                    End If
                End If
            End If

            frmInfoLoad.Show()
            frmInfoLoad.Refresh() 'nécessair pour afficher son contenu

            'Cette partie permet de remplir les listes locales de la form pour le rechargement de la scène
            Me.lstRessources.Items.Clear()
            Me.lstTypeRessources.Items.Clear()
            Me.txtNomScene = frmViewerload.txtNomscene
            For i As Integer = 0 To frmViewerload.lstRessources.Items.Count - 1
                Me.lstRessources.Items.Add(frmViewerload.lstRessources.Items(i))
            Next
            For i As Integer = 0 To frmViewerload.lstTypeRessources.Items.Count - 1
                Me.lstTypeRessources.Items.Add(frmViewerload.lstTypeRessources.Items(i))
            Next

            'Et on initialise la scène
            Me.InitScene()
            LoadAxe() 'On ajoute les axes
            Me.InitTree(mySceneManager.RootSceneNode, Nothing) 'On remplit la treeview

            frmViewerload.Dispose() 'et on détruit la form de chargement

            'On ajoutes la gestion des évèbements aux champs texte
            AddHandler txtPositionY.ValueChanged, AddressOf Position
            AddHandler txtPositionZ.ValueChanged, AddressOf Position

            AddHandler txtScaleY.ValueChanged, AddressOf Echelle
            AddHandler txtScaleZ.ValueChanged, AddressOf Echelle

            'On détruit la form d'attente
            frmInfoLoad.Hide()
            frmInfoLoad.Dispose()
            Cursor = Cursors.Default

            'On démarre le timer pour le rendu et la gestion des touches
            'timerRendu.Enabled = True
            init = False 'L'initialisation est terminé, la variable init passe à false

            Me.Show() 'Tout est fini, on affiche la fenêtre

            'myRoot.StartRendering()
            While RenduEnCours
                My.Application.DoEvents()
                'Affichage des infos de rendu
                Try
                    lblAvg.Text = "FPS moyennes: " & Mogre.StringConverter.ToString(MyWindow.AverageFPS, 3)
                    lblCurr.Text = "FPS courantes: " & Mogre.StringConverter.ToString(MyWindow.LastFPS, 3)
                    lblBest.Text = "Meilleures FPS: " & Mogre.StringConverter.ToString(MyWindow.BestFPS, 3)
                    lblWorst.Text = "Pires FPS: " & Mogre.StringConverter.ToString(MyWindow.WorstFPS, 3)
                    lblNumTris.Text = "Triangles: " & Mogre.StringConverter.ToString(MyWindow.TriangleCount)
                    lblNumBatches.Text = "Nombre de Batch: " & Mogre.StringConverter.ToString(MyWindow.BatchCount)
                Catch ex As Exception

                End Try

                'et on rend une frame
                myRoot.RenderOneFrame()

                'Update du monde physique
                If mnuDetectionDeCollision.Checked Then
                    Dim stepPhysics As Single = 0.1
                    myWorld.Update(stepPhysics)
                End If

                If SceneNodeSelected Is Nothing Then
                    'si un objet est sélectionné ou pas, on active ou désactive le panneau de paramètre
                    grpParamObjet.Enabled = False
                    cmdResetEtat.Enabled = False
                Else
                    grpParamObjet.Enabled = True
                    cmdResetEtat.Enabled = True
                End If
            End While
            'Sur des machines rapide, le nombre trop élevé de FPS rend la rotation sur X des plus étrange si la détection de collisions est activée.
            'Le renderOneFrame rendant moins de FPS, je l'utilise dans le timer.
            'C'est surement une histoire de facteur de multiplication lors de la récupération de camera_rotation_x, mais je n'ai pas trouvé la formule magique.
            DisposeNewton()
            If mnuDetectionDeCollision.Checked Then
                DisposeOgre()
            End If
        Else

            'Sinon on s'en va
            Me.Dispose()
        End If

    End Sub

    Private Sub frmMain_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
        'On se contente d'arrêter le timer et à positionner la vaiable permettant de stopper le rendu Ogre, le reste des ressources ser libéré par le garbage collector (en tout cas on l'espère ;o) )
        'timerRendu.Enabled = False
        RenduEnCours = False
    End Sub

#End Region

#Region "Timer"

    Private Sub timerRendu_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles timerRendu.Tick

        ''Affichage des infos de rendu
        'Try
        '    lblAvg.Text = "FPS moyennes: " & Mogre.StringConverter.ToString(MyWindow.AverageFPS, 3)
        '    lblCurr.Text = "FPS courantes: " & Mogre.StringConverter.ToString(MyWindow.LastFPS, 3)
        '    lblBest.Text = "Meilleures FPS: " & Mogre.StringConverter.ToString(MyWindow.BestFPS, 3)
        '    lblWorst.Text = "Pires FPS: " & Mogre.StringConverter.ToString(MyWindow.WorstFPS, 3)
        '    lblNumTris.Text = "Triangles: " & Mogre.StringConverter.ToString(MyWindow.TriangleCount)
        '    lblNumBatches.Text = "Nombre de Batch: " & Mogre.StringConverter.ToString(MyWindow.BatchCount)
        'Catch ex As Exception

        'End Try

        ''et on rend une frame
        ''myRoot.RenderOneFrame()

        ''Update du monde physique
        'If mnuDetectionDeCollision.Checked Then
        '    Dim stepPhysics As Single = 0.1
        '    myWorld.Update(stepPhysics)
        'End If

        'If SceneNodeSelected Is Nothing Then
        '    'si un objet est sélectionné ou pas, on active ou désactive le panneau de paramètre
        '    grpParamObjet.Enabled = False
        '    cmdResetEtat.Enabled = False
        'Else
        '    grpParamObjet.Enabled = True
        '    cmdResetEtat.Enabled = True
        'End If

    End Sub

#End Region

#Region "Picture Box (Pour le rendu Ogre et le choix des couleurs de la lumière)"

    Private Sub picMogre_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picMogre.MouseDown

        'Quand on clique dans la picture box, on active le déplacement si c'est avec le bouton gauche

        If e.Button = Windows.Forms.MouseButtons.Left Then
            moving = True
            Cursor.Hide()
        End If

    End Sub

    Private Sub picMogre_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picMogre.MouseUp

        'Et on désactive le déplacement quand on relache le bouton gauche

        If e.Button = Windows.Forms.MouseButtons.Left Then
            moving = False
            Cursor.Show()
        End If
    End Sub

    Private Sub picSpecular_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles picSpecular.DoubleClick

        'On passe les paramètres à la form de sélection des couleurs (la lumière sélectionner et l'action à effectuer)
        frmColorDialogue.myLight = LightSelected
        frmColorDialogue.Provenance = "Specular"

        'On ouvre la form
        frmColorDialogue.ShowDialog()

        'on la libère
        frmColorDialogue.Dispose()

    End Sub

    Private Sub picDiffuse_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles picDiffuse.DoubleClick

        'On passe les paramètres à la form de sélection des couleurs (la lumière sélectionner et l'action à effectuer)
        frmColorDialogue.myLight = LightSelected
        frmColorDialogue.Provenance = "Diffuse"

        'On ouvre la form
        frmColorDialogue.ShowDialog()

        'on la libère
        frmColorDialogue.Dispose()

    End Sub

    Private Sub picMogre_SizeChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles picMogre.SizeChanged

        'Pour redimensionner le vieport de manière correcte quant on redimmensionne la picturebox (du à un resize de la form ou un déplacement des splitter)

        If Not init Then 'on ne le fait qu'une fois l'initalisation terminée.

            timerRendu.Enabled = False 'on arrête le timer (on ne va pas rendre pendant que le vieport n'existe plus...)

            Try

                'on récupère le viewport
                Dim myViewport As Mogre.Viewport = MyWindow.GetViewport(0)

                'on récupère la couleur du vieport car elle est perdu à sa destruction
                Dim CouleurVieport As Mogre.ColourValue = myViewport.BackgroundColour

                'On le supprime de la fenêtre de rendu
                MyWindow.RemoveViewport(myViewport.ZOrder)

                'Et on efface la feneêtre de rendu
                myRoot.DetachRenderTarget(MyWindow)
                MyWindow.Dispose()
                MyWindow = Nothing

                'On recrè la fenêtre, qui prendra comme dimension la nouvelle taille du contrôle
                Dim misc As NameValuePairList = New NameValuePairList
                misc("externalWindowHandle") = picMogre.Handle.ToString
                Dim const_list As Const_NameValuePairList = misc.ReadOnlyInstance
                MyWindow = myRoot.CreateRenderWindow("OgreWieport", 0, 0, False, const_list)

                'On recrè un viewport avec les nouveaux paramètrex
                myViewport = MyWindow.AddViewport(myCamera)
                'on réaffecte la couleur précédemment sauvegardée
                myViewport.BackgroundColour = CouleurVieport
                'on fixe l'aspect ratio de la caméra
                myCamera.AspectRatio = myViewport.ActualWidth / myViewport.ActualHeight

            Catch ex As Exception
                'en cas d'erreur....
                If OgreException.IsThrown Then
                    Try 'parfois ça plante ici car OgreException.LastException n'est pas défini, donc je réimbrique un try
                        MsgBox(OgreException.LastException.FullDescription, MsgBoxStyle.Critical, _
                             "Exeption OGRE!")

                    Catch ex2 As Exception

                    End Try
                Else
                    MsgBox(ex.ToString, "Erreur")
                End If
            End Try

            timerRendu.Enabled = True 'on redémarre le timer 

        End If

    End Sub

#End Region

#Region "Champs texte"

    Private Sub txtFOV_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtFOV.TextChanged
        'Permet de changer l'angle du champ de vision
        If txtFOV.Text <> "" Then
            Try
                myCamera.FOVy = CType(txtFOV.Text, Mogre.Radian)
            Catch ex As Exception

            End Try
        End If
    End Sub

    Private Sub Echelle(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtScaleX.ValueChanged

        'Appeller par les 3 txtScale, permet de changer le facteur d'échelle de l'objet sélectionné
        If Not EnCoursDeSelection Then 'nécessaire car si on est en cours de sélection d'un objet, de mauvais paramètres sont appliqués
            SceneNodeSelected.SetScale(txtScaleX.Value, txtScaleY.Value, txtScaleZ.Value) 'et on applique l'échelle
        End If
    End Sub

    Private Sub Position(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtPositionX.ValueChanged, txtPositionX.Scroll

        'Appeller par les 3 txtPosition, permet de changer la position de l'objet sélectionné

        If Not EnCoursDeSelection And Not EnCoursDeTranslation Then 'nécessaire car si on est en cours de sélection d'un objet, de mauvais paramètres sont appliqués
            SceneNodeSelected.SetPosition(txtPositionX.Value, txtPositionY.Value, txtPositionZ.Value) 'et on applique la position
        End If
    End Sub

#End Region

#Region "Les menus"

    Private Sub mnuParamOgre_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuParamOgre.Click
        'Montre la fenêtre de configuration OGRE
        myRoot.ShowConfigDialog()

        'Une fois les paramètres modifiés, on recharge la scène
        mnuRecharger_Click(sender, e)
    End Sub

    Private Sub mnuOuvrir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuOuvrir.Click

        Me.Hide() 'On cache la fenêtre principale

        'Affichage de la fenêtre de choix de la scène à charger
        frmViewerload.ShowDialog()

        If frmViewerload.bValide Then
            'Si on a valider le choix.....
            'On affiche la form d'attente
            frmInfoLoad.Show()
            frmInfoLoad.Refresh() 'nécessair pour afficher son contenu
            Cursor = Cursors.WaitCursor 'juste pour la beauté

            'timerRendu.Enabled = False 'on arrête le timer

            DisposeNewton()
            DisposeOgre() 'on libère les ressources Ogre

            'on reinitialise les checkboxs
            chkTracking.Checked = False
            chkAxe.Checked = False

            'on vide le treeview et on recrè les 3 noeuds de base
            treeScene.Nodes.Clear()
            treeScene.Nodes.Add("Root")
            treeScene.Nodes.Item(0).Nodes.Add("Autres")
            treeScene.Nodes.Item(0).Nodes.Add("Cameras")
            treeScene.Nodes.Item(0).Nodes.Add("Lights")
            treeScene.Nodes.Item(0).Nodes.Add("Entities")

            'on vide les listes héritées de la forme de chargement pour le menu recharger
            Me.lstRessources.Items.Clear()
            Me.lstTypeRessources.Items.Clear()
            Me.txtNomScene = frmViewerload.txtNomscene

            'et on les remplit avec les nouvelles valeur
            For i As Integer = 0 To frmViewerload.lstRessources.Items.Count - 1
                Me.lstRessources.Items.Add(frmViewerload.lstRessources.Items(i))
            Next
            For i As Integer = 0 To frmViewerload.lstTypeRessources.Items.Count - 1
                Me.lstTypeRessources.Items.Add(frmViewerload.lstTypeRessources.Items(i))
            Next

            'On recrè le scène manager
            mySceneManager = myRoot.CreateSceneManager(SceneType.ST_GENERIC)

            If frmViewerload.chkPhysique.Checked Then
                mnuDetectionDeCollision.Checked = True
                mnuGravite.Enabled = True
                mnuDebugActif.Enabled = True
            End If

            'Affichage de la fenêtre de paramètres OGRES, le cas échéant
            If frmViewerload.chkOgre.Checked Then
                If Not myRoot.ShowConfigDialog Then
                    Exit Sub
                End If
            Else
                If Not myRoot.RestoreConfig() Then
                    If Not myRoot.ShowConfigDialog Then
                        Exit Sub
                    End If
                End If
            End If

            'Et on initialise la scène
            Me.InitScene()
            LoadAxe() 'On charge les axes
            Me.InitTree(mySceneManager.RootSceneNode, Nothing) 'on remplit la treeview

            'On détruit la form d'attente
            frmInfoLoad.Hide()
            frmInfoLoad.Dispose()
            Cursor = Cursors.Default

            Me.Show() 'et on réaffiche la fenêtre

            'on libère la form de chargement
            frmViewerload.Dispose()

            'On démarre le timer pour le rendu et la gestion des touches
            'timerRendu.Enabled = True

        End If
    End Sub

    Private Sub mnuCouleurDuViewport_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuCouleurDuViewport.Click

        'on récupère le vieport
        Dim myViewport As Mogre.Viewport = MyWindow.GetViewport(0)

        'pour le passer en paramètre à la fenêtre de choix des couleurs
        frmColorDialogue.myViewPort = myViewport
        frmColorDialogue.Provenance = "Viewport"

        'on l'affiche
        frmColorDialogue.ShowDialog()

        'et on la libère
        frmColorDialogue.Dispose()

    End Sub

    Private Sub mnuRecharger_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuRecharger.Click
        'Permet de recharger la scène en cours

        Cursor = Cursors.WaitCursor
        Me.Hide()
        'On affiche la form d'attente
        frmInfoLoad.Show()
        frmInfoLoad.Refresh() 'nécessair pour afficher son contenu

        'on arrête le timer
        'timerRendu.Enabled = False

        'on détruit nos différentes interfaces
        DisposeNewton()
        DisposeOgre()
        treeScene.Nodes.Clear()
        treeScene.Nodes.Add("Root")
        'on recrè les noeuds de base du treeview
        treeScene.Nodes.Item(0).Nodes.Add("Autres")
        treeScene.Nodes.Item(0).Nodes.Add("Cameras")
        treeScene.Nodes.Item(0).Nodes.Add("Lights")
        treeScene.Nodes.Item(0).Nodes.Add("Entities")

        'on initialise le scenemanager
        mySceneManager = myRoot.CreateSceneManager(SceneType.ST_GENERIC)

        'Et on initialise la scène
        Me.InitScene()
        LoadAxe() 'on charge les axes
        Me.InitTree(mySceneManager.RootSceneNode, Nothing) 'on initialise le treeview

        'On détruit la form d'attente
        frmInfoLoad.Hide()
        frmInfoLoad.Dispose()
        Me.Show()
        Cursor = Cursors.Default

        'On démarre le timer pour le rendu et la gestion des touches
        'timerRendu.Enabled = True
    End Sub

    Private Sub mnuAxeDuMonde_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuAxeDuMonde.Click
        'permet de montrer/cacher l'axe du monde

        If mnuAxeDuMonde.Checked Then
            NodeAxeMonde.SetVisible(True)
        Else
            NodeAxeMonde.SetVisible(False)
        End If
    End Sub

    Private Sub mnuOmbres_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuOmbres.Click
        'Permet d'afficher ou pas les ombres on fonction de la technique choisie

        If mnuOmbres.Checked Then
            mySceneManager.ShadowTechnique = MyShadowTechnique
        Else
            mySceneManager.ShadowTechnique = ShadowTechnique.SHADOWTYPE_NONE
        End If
    End Sub

    Private Sub mnuShadowType_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuShadowType.SelectedIndexChanged
        'C'est un menu combbox pour la simple raison que le traitement du choix est simplifié (pas à se paluche les évènements de 6 menu, juste une sélection dans une combo)

        Select Case mnuShadowType.SelectedIndex
            Case 0
                MyShadowTechnique = ShadowTechnique.SHADOWTYPE_STENCIL_MODULATIVE
            Case 1
                MyShadowTechnique = ShadowTechnique.SHADOWTYPE_STENCIL_ADDITIVE
            Case 2
                MyShadowTechnique = ShadowTechnique.SHADOWTYPE_TEXTURE_MODULATIVE
            Case 3
                MyShadowTechnique = ShadowTechnique.SHADOWTYPE_TEXTURE_ADDITIVE
            Case 4
                MyShadowTechnique = ShadowTechnique.SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED
            Case 5
                MyShadowTechnique = ShadowTechnique.SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED
        End Select

        'si le menu d'affichage des ombres est coché, on applique directe
        If mnuOmbres.Checked Then
            mySceneManager.ShadowTechnique = MyShadowTechnique
        End If
    End Sub

    Private Sub mnuCouleurOmbres_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuCouleurOmbres.Click

        'On passe les paramètre à la forme de choix des couleurs
        frmColorDialogue.mySceneManager = mySceneManager
        frmColorDialogue.Provenance = "Ombres"

        'on l'affiche
        frmColorDialogue.ShowDialog()

        'et on la libère
        frmColorDialogue.Dispose()

    End Sub

    Private Sub mnuLumireAmbiante_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuLumireAmbiante.Click

        'On passe les paramètre à la forme de choix des couleurs
        frmColorDialogue.mySceneManager = mySceneManager
        frmColorDialogue.Provenance = "Ambiante"

        'on l'affiche
        frmColorDialogue.ShowDialog()

        'et on la libère
        frmColorDialogue.Dispose()

    End Sub

    Private Sub mnuDetectionDeCollision_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuDetectionDeCollision.Click
        mnuOptions.HideDropDown()
        If mnuDetectionDeCollision.Checked Then
            mnuGravite.Enabled = True
            'mnuGravite.Checked = True
            mnuDebugActif.Enabled = True
            mnuRecharger_Click(sender, e)
        Else
            mnuGravite.Enabled = False
            mnuGravite.Checked = False
            mnuDebugActif.Enabled = False
            mnuRecharger_Click(sender, e)
        End If
    End Sub

    Private Sub mnuDebugActif_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuDebugActif.Click
        If mnuDebugActif.Checked Then
            MogreNewt.Debugger.Instance.ShowLines(myWorld)
        Else
            MogreNewt.Debugger.Instance.HideLines()
        End If
    End Sub

    Private Sub mnuHauteurCamera_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuHauteurCamera.TextChanged

        'Permet de changer la hauteur de la caméra en temps réel

        Try
            If Not init Then
                Me.CamSize.y = mnuHauteurCamera.Text
                Me.CamNode.SetScale(Me.CamSize)
            End If

        Catch ex As Exception

        End Try

    End Sub

#End Region

#Region "Le treeview"

    Private Sub treeScene_AfterSelect(ByVal sender As System.Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles treeScene.AfterSelect

        'si on est pas en cours de chargement de la scène initiale, on traite la sélection du treeview
        If Not init Then

            'on cache le groupe pour la gestion de la lumière
            grpLumiere.Visible = False

            'on est en cours de sélection d'un objet donc : 
            EnCoursDeSelection = True

            'chkAxe.Checked = False

            If Not SceneNodeSelected Is Nothing Then 'si le scenenodeselected précédent n'est pas vide, on cache sa bounding box
                SceneNodeSelected.ShowBoundingBox = False
            End If

            Try
                Dim tree As TreeView = sender 'on récupère le sender dans un objet typé (plus pratique pour coder
                SceneNodeSelected = tree.SelectedNode.Tag 'on récupère le scenenode dans le tag du treenode
                Dim ObjectType As String 'pour le test du type d'objet sélectionné

                Try
                    MObjectSelected = SceneNodeSelected.GetAttachedObject(tree.SelectedNode.Name) 'on récupère l'objet contenu dans le scenenode
                Catch
                End Try

                If Not e.Node.Text.ToUpper.Contains("ROOT") Then 'si on est pas sur root, alors :
                    Try
                        ObjectType = MObjectSelected.MovableType 'on récupère le type de l'objet
                        Select Case ObjectType
                            Case "Entity" 'si c'est une entity 
                                chkTracking.Checked = False
                                chkTracking.Enabled = True
                                grpParamObjet.Enabled = True
                            Case "Camera" 'si c'est une caméra
                                grpParamObjet.Enabled = False
                                myCamera = mySceneManager.GetCamera(MObjectSelected.Name) 'on récupère son nom
                                Dim ViewPort As Mogre.Viewport = MyWindow.GetViewport(0) 'on récupère le premier viewport
                                ViewPort.Camera = myCamera 'et on change de caméra
                            Case "Light" 'si c'est une lumière
                                grpLumiere.Visible = True
                                grpParamObjet.Enabled = True
                                chkTracking.Checked = False
                                chkTracking.Enabled = True
                                LightSelected = MObjectSelected

                                'on paramètre les picturesbox représentant la couleur de la lumière avec ses couleurs, justement
                                picDiffuse.BackColor = Color.FromArgb(LightSelected.DiffuseColour.GetAsARGB())
                                picSpecular.BackColor = Color.FromArgb(LightSelected.SpecularColour.GetAsARGB())
                                'et les trackbar pour régler l'atténuation
                                trkRange.Value = LightSelected.AttenuationRange * 100
                                trkConstant.Value = LightSelected.AttenuationConstant * 100
                                trkLinear.Value = LightSelected.AttenuationLinear * 100
                                trkQuadratic.Value = LightSelected.AttenuationQuadric * 100

                        End Select

                        grpParamObjet.Text = "Modifications objet : " + SceneNodeSelected.Name

                    Catch ex As Exception
                        'si on est en erreur, on désactive tout

                        chkTracking.Checked = False
                        chkTracking.Enabled = True
                        grpLumiere.Visible = False
                        grpParamObjet.Enabled = False

                    End Try

                    Try
                        'on montre la bounding box de l'objet sélectionné
                        SceneNodeSelected.ShowBoundingBox = True
                    Catch ex As Exception
                    End Try

                    RemplitGrille() 'on remplit la grille de paramètres de l'objet
                    EnCoursDeSelection = False 'la sélection est terminée

                    If ShowAxes Then
                        chkAxe.Checked = True 'si on doit montrer l'axe de l'objet, on le fait
                    End If

                    If TrackObjet Then
                        chkTracking.Checked = True 'si l'autotrackin est actif, alors on le remet
                    End If

                End If
            Catch ex As System.Runtime.InteropServices.SEHException
                'en cas d'erreur....on ne fait rien de spécial
            End Try
        End If

    End Sub

    Private Sub treeScene_BeforeSelect(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) Handles treeScene.BeforeSelect
        'Si l'axe de l'objet est affiché
        If ShowAxes Then
            chkAxe.Checked = False 'on va déclencher l'évènement changed de la checkbox
        End If
        If TrackObjet Then ' on fait pareil pour l'autotracking
            chkTracking.Checked = False
        End If
    End Sub

#End Region

#Region "Les check box"

    Private Sub chkTracking_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles chkTracking.CheckedChanged
        'Permet d'activer/désactiver l'autotracking de l'objet sélectionné par la caméra
        Try
            myCamera.SetAutoTracking(False)
            If chkTracking.Checked Then
                myCamera.SetAutoTracking(True, SceneNodeSelected)
            End If
        Catch ex As Exception

        End Try
    End Sub

    Private Sub chkAxe_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles chkAxe.CheckedChanged
        'Permet de montrer/cacher l'axe de l'objet sélectionné

        If Not EnCoursDeSelection Then 'si on est pas en cours de sélection de l'objet

            Try
                If chkAxe.Checked Then
                    'Lorsque la case est cochée
                    mySceneManager.RootSceneNode.RemoveChild(NodeAxe) 'on détache l'axe de la racine
                    SceneNodeSelected.AddChild(NodeAxe) 'on l'attache à l'objet sélectionnée
                    NodeAxe.SetPosition(0, 0, 0) 'au centre de ce dernier
                    NodeAxe.SetOrientation(SceneNodeSelected.Orientation.w, SceneNodeSelected.Orientation.x, SceneNodeSelected.Orientation.y, SceneNodeSelected.Orientation.z) 'et avec l'orientation de ce dernier
                    NodeAxe.SetVisible(True) 'et on le montre
                Else
                    'lorsque la case est décochée, on cache le Node de l'axe
                    NodeAxe.SetVisible(False)
                    SceneNodeSelected.RemoveChild(NodeAxe) 'on l'enlève de l'objet sélectionné
                    mySceneManager.RootSceneNode.AddChild(NodeAxe) 'et on l'attache à la racine
                End If
            Catch
                'Juste au cas ou...
            End Try

        End If
    End Sub

    Private Sub chkAxe_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles chkAxe.Click
        'On inverse simplement la variable permettant de savoir si on affiche ou pas l'axe des objets
        ShowAxes = Not ShowAxes
    End Sub

    Private Sub chkTracking_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles chkTracking.Click
        'On inverse simplement la variable permettant de savoir si on est en autotracking des objets
        TrackObjet = Not TrackObjet
    End Sub

#End Region

#Region "La grille"

    Private Sub grdDetail_CellDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles grdDetail.CellDoubleClick

        'Lors d'un double clique sur les cellules de la grille, on permet le choix sur boundingbox et visible (les autres valeurs sont modifiée par les champs en entête de la form
        Dim rectangle As New System.Drawing.Rectangle 'le rectangle correspondant à la cellule sélectionnée
        Dim Cell As DataGridViewCell = sender.currentcell 'on récupère la cellule sélectionnée
        Dim Row As DataGridViewRow = sender.rows(Cell.RowIndex) 'la ligne sélectionnée
        Dim FirstCell As DataGridViewCell = Row.Cells(0) 'La première cellule

        rectangle = Me.grdDetail.GetCellDisplayRectangle(Cell.ColumnIndex, Cell.RowIndex, True) 'on paramètre le rectangle
        If Cell.Value = "Oui" Then 'suivant la valeur de la cellule, on paramètre la combobox
            Me.cboBoolean.SelectedIndex = 0
        Else
            Me.cboBoolean.SelectedIndex = 1
        End If

        'on positionne et dimmensionne la combobox pour l'afficher dans la cellule
        Me.cboBoolean.Top = rectangle.Top
        Me.cboBoolean.Left = rectangle.Left
        Me.cboBoolean.Width = rectangle.Width
        Me.cboBoolean.Height = rectangle.Height
        Me.cboBoolean.Tag = FirstCell.Value 'pour savoir sur quel paramètre de la grille on travail (cf. cboBoolean.SelectedIndexChanged)

        'et on la montre
        Me.cboBoolean.Visible = True
    End Sub

#End Region

#Region "Les Command Buttons"

    Private Sub cmdSetDirection_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdSetDirection.Click
        'Permet de fixer la direction de l'objet en fonction des 3 champs textes
        SceneNodeSelected.SetDirection(txtDirectionX.Value, txtDirectionY.Value, txtDirectionZ.Value)
        GetOrientation() 'pour afficher les nouvelles valeurs
    End Sub

    Private Sub cmdPitch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdPitch.Click
        'On tourne sur X
        SceneNodeSelected.Pitch(txtRotationX.Value)
        'Si l'axe est affiché, même punition. Réaction bizarre si la vaiable inheritorientation est mise à true.
        If ShowAxes Then
            NodeAxe.Pitch(txtRotationX.Value)
        End If
        GetOrientation() 'on affiche la nouvelle orientation
    End Sub

    Private Sub cmdYAw_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdYAw.Click
        'cf. cmdPich_Click sauf que c'est sur Y
        SceneNodeSelected.Yaw(txtRotationY.Value)
        If ShowAxes Then
            NodeAxe.Yaw(txtRotationY.Value)
        End If
        GetOrientation()
    End Sub

    Private Sub cmdRoll_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdRoll.Click
        'cf. cmdPich_Click sauf que c'est sur Z
        SceneNodeSelected.Roll(txtRotationZ.Value)
        If ShowAxes Then
            NodeAxe.Roll(txtRotationZ.Value)
        End If
        GetOrientation()
    End Sub

    Private Sub cmdResetOrientation_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdResetOrientation.Click
        'Permet de mettre l'objet dans sa direction de création, avant toute rotation
        SceneNodeSelected.ResetOrientation()
        GetOrientation()
    End Sub

    Private Sub cmdSetorientation_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdSetorientation.Click
        'Permet de ficer l'orientation de l'objet en fonction des 4 paramètre choisis
        SceneNodeSelected.SetOrientation(txtOrientationW.Value, txtOrientationX.Value, txtOrientationY.Value, txtOrientationZ.Value)
    End Sub

    Private Sub cmdResetEtat_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdResetEtat.Click
        'Permet de réinitialiser l'objet à ses conditions d'origine
        EnCoursDeSelection = True
        SceneNodeSelected.ResetToInitialState()
        'on récupère les nouvelles valeurs pour les afficher
        GetPosition()
        GetOrientation()
        GetEchelle()
        EnCoursDeSelection = False
    End Sub

    Private Sub cmdSetTranslation_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdSetTranslation.Click

        'Permet de déplacer l'objet en fonction de l'axe choisi
        Dim Axe As Mogre.Node.TransformSpace
        EnCoursDeTranslation = True 'on commence la translation

        'On récupère l'axe choisi dans la combobox
        Select Case cboTransAxe.SelectedIndex
            Case 0
                Axe = Node.TransformSpace.TS_LOCAL
            Case 1
                Axe = Node.TransformSpace.TS_PARENT
            Case 2
                Axe = Node.TransformSpace.TS_WORLD
        End Select

        'et on translate en fonction des paramètres sélectionés
        SceneNodeSelected.Translate(txtTransX.Value, txtTransY.Value, txtTransZ.Value, Axe)
        GetPosition() 'on récupère la nouvelle position
        EnCoursDeTranslation = False 'la translation est terminée

    End Sub

    Private Sub cmdReinitCamera_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdReinitCamera.Click
        'Pour réinitialiser la caméra à sa position d'origine
        If mnuDetectionDeCollision.Checked Then
            CamBody.SetPositionOrientation(InitialCamBodyPos, InitialCamBodyOrient)
        Else
            myCamera.SetPosition(InitialCamBodyPos.x, InitialCamBodyPos.y, InitialCamBodyPos.z)
            myCamera.Orientation = InitialCamBodyOrient
        End If
    End Sub

#End Region

#Region "Les ComboBox"

    Private Sub cboBoolean_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboBoolean.SelectedIndexChanged

        'Si la combobox est visible, alors on est en tains de choisir l'option
        If cboBoolean.Visible Then
            If cboBoolean.Tag = "Visible" Then 'si ça porte sur la visibilité de l'objet
                If cboBoolean.SelectedIndex = 0 Then
                    MObjectSelected.Visible = True
                Else
                    MObjectSelected.Visible = False
                End If
            ElseIf cboBoolean.Tag = "CastShadows" Then 'sur sa faculté à projeter une ombre
                If cboBoolean.SelectedIndex = 0 Then
                    MObjectSelected.CastShadows = True
                Else
                    MObjectSelected.CastShadows = False
                End If
            Else 'alors c'est la boundingbox
                If cboBoolean.SelectedIndex = 0 Then
                    SceneNodeSelected.ShowBoundingBox = True
                Else
                    SceneNodeSelected.ShowBoundingBox = False
                End If
            End If
            cboBoolean.Visible = False 'on cache la combobox
            RemplitGrille() 'et on reremplit la grille
        End If

    End Sub

#End Region

#Region "Les trackbars"

    Private Sub trkRange_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles trkRange.ValueChanged
        'Modification de la portée de la lumière
        lblRange.Text = trkRange.Value
        LightSelected.SetAttenuation(trkRange.Value, trkConstant.Value / 100, System.Math.Round(trkLinear.Value / 10000, 5), System.Math.Round(trkQuadratic.Value / 10000, 5))
        'Les valeurs sont divisée pour s'accorder au range des paramètres. A affiner, pour voir plus ou moins d'effets
    End Sub

    Private Sub trkConstant_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles trkConstant.ValueChanged
        'La constante d'atténuation
        lblConstant.Text = trkConstant.Value / 100
        LightSelected.SetAttenuation(trkRange.Value, trkConstant.Value / 100, System.Math.Round(trkLinear.Value / 10000, 5), System.Math.Round(trkQuadratic.Value / 10000, 5))
        'Les valeurs sont divisée pour s'accorder au range des paramètres. A affiner, pour voir plus ou moins d'effets
    End Sub

    Private Sub trkLinear_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles trkLinear.ValueChanged
        'La linéarité de l'atténuation
        lblLinear.Text = System.Math.Round(trkLinear.Value / 10000, 5)
        LightSelected.SetAttenuation(trkRange.Value, trkConstant.Value / 100, System.Math.Round(trkLinear.Value / 10000, 5), System.Math.Round(trkQuadratic.Value / 10000, 5))
        'Les valeurs sont divisée pour s'accorder au range des paramètres. A affiner, pour voir plus ou moins d'effets
    End Sub

    Private Sub trkQuadratic_ValueChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles trkQuadratic.ValueChanged
        'La valeur Quadratic (Pas tout compris là...)
        lblQuadratic.Text = System.Math.Round(trkQuadratic.Value / 10000, 5)
        LightSelected.SetAttenuation(trkRange.Value, trkConstant.Value / 100, System.Math.Round(trkLinear.Value / 10000, 5), System.Math.Round(trkQuadratic.Value / 10000, 5))
        'Les valeurs sont divisée pour s'accorder au range des paramètres. A affiner, pour voir plus ou moins d'effets
    End Sub

#End Region

#End Region

End Class

Conclusion :


Attention, la source ci-dessus n'est pas utilisable tel quel, vous devez télécharger le projet pour celà.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.