Pyogre/Tutoriel basique 1

Tutoriel basique 1: Les constructeurs SceneNode, Entity et SceneManager. modifier

Note: Ce tutoriel est la traduction d'un tutoriel était écrit pour Python-Ogre 1.0 qui est basé sur la série de tutoriels basiques disponibles sur le Wiki d'Ogre (en).


Pré-requis modifier

Ce tutoriel requiert que vous ayez des connaissances en programmation en Python et que vous ayez déjà installé PyOgre. Aucune connaissance de Ogre n'est requise pour ce tutoriel.


Introduction modifier

Dans ce tutoriel je vais vous introduire les constructeurs les plus basiques de Ogre: SceneManager, SceneNode, et les objets Entity. Nous n'étudierons pas une grande quantité de code; à la place je vais me concentrer sur les concepts généraux pour que vous commenciez à comprendre Ogre.

Pendant ce tutoriel, vous devriez ajouter peu à peu du code pour votre projet et regarder le résultat pendant que nous le construisons. Il n'y a pas d'autre moyens pour être familié avec ces concepts! Essayez de ne pas simplement lire le code complet. Si vous avez des problèmes vous pouvez regarder l'exemple de code source complet situé à la fin de ce tutoriel.

Notez que ce programme a besoin d'utiliser le fichier SampleFramework.py. Vous pouvez le trouver dans le dossier demos/. Vous devrez aussi vous assurer d'avoir les fichiers resources.cfg et media.cfg dans le même dossier que votre code source, et que le fichier resources.cfg pointe sur le dossier correct pour votre dossier Media. Si vous voulez le faire «à l'arrache», la meilleure façon de faire ceci est de créer le code dans le même dossier que les exemples de PyOgre.


C'est parti ! modifier

Code de base modifier

Nous allons utiliser un code de base pour ce tutoriel. Vous devez pour l'instant ignorer tout le code, sauf ce que l'on va ajouter dans la méthode _createScene. Dans un prochain tutoriel, j'expliquerais le fonctionnement de Ogre, mais tout de suite, nous allons débuter avec un niveau très simple. Créez un fichier source python appelé "basic_01.py" et ajoutez y ce code :

import ogre.renderer.OGRE as ogre
import SampleFramework as sf

class TutorialApplication(sf.Application):
   
   def _createScene(self):
       pass

if __name__ == '__main__':
   ta = TutorialApplication()
   ta.go()

Dès que le programme marche, utilisez les touches WASD (ZQSD?) pour vous déplacer, et la souris pour regarder autour de vous. La touche Échap existe dans le programme. Par contre, tant que nous n'aurons ajouté aucun objet dans la scène, il n'y aura rien à regarder.


Comment marche Ogre modifier

C'est un vaste sujet. Nous commencerons avec les SceneManagers et continuerons notre chemin vers les !MovableObjects et les SceneNodes. Ces trois classes sont les blocs principaux de construction d'applications avec Ogre.

Les bases de SceneManager modifier

Tout ce qui va apparaître à l'écran sera géré par le SceneManager. Quand vous placez vos objets dans la scène, le SceneManager est la classe qui les garde en mémoire. Quand vous créez une caméra pour voir la scène (que nous compléterons dans un prochain tutoriel) le SceneManager la garde en mémoire. Quand vous créez des plans, des tableaux des lumières ... le SceneManager les garde en mémoire.

Il y a plusieurs type de SceneManagers. Celui qui fait un rendu des terrains, celui qui fait un rendu des cartes BSP etc... Vous pouvez voir une liste des SceneManagers existants ici. Nous en verrons plus sur ces SceneManagers en progressant dans les tutoriels.

Les bases de MoveableObject modifier

Un MovableObject est un objet qui peut être placé dans une scène et être déplacé.

Une entité est un des types d'objets que vous pouvez afficher dans une scène (il est une sous-classe de MovableObject). Vous pouvez penser qu'une entité peut être représentée par un mesh 3D. Un robot pourrait être une entité, un poisson peut être une entité, le terrain sur lequel vos personnages marchent peut être un grande entité. Mais des lumières, Billboard, particules, caméra etc... ne peuvent pas être une entité, mais ils sont des !MovableObjects.

Une chose à noter à propos d'Ogre est qu'il sépare les objets affichable (renderable) en fonction de leur location et orientation. Ceci signifie que vous ne pouvez pas placer une entité directement dans une scène. À la place, vous devez attacher une entité à un objet !SceneNode, cet objet contient les informations tels que la location et l'orientation.

Les bases de SceneNode modifier

Ça a déjà été mentionné, SceneNodes garde en mémoire la localisation et l'orientation pour tous les objets qui y sont attachés. Quand vous créez une entité, elle ne sera pas rendue sur la scène tant que vous ne l'attachez pas à un SceneNode. Similairement, un SceneNode n'est pas un objet qui s'affiche à l'écran. Une fois un SceneNode crée, et attaché à une entité (ou un autre objet) permettra d'afficher l'objet à l'écran.

Les SceneNodes peuvent être attaché à n'importe quel nombre d'objets. Imaginez que vous avez un personnage marchant à l'écran et que vous désirez générer une lumière autour de lui. La manière de le faire va être de créer un premier SceneNode, puis de créer une entité pour le personnage et l'attacher à ce SceneNode. Ensuite, vous pouvez créer un objet lumière et l'attacher au SceneNode. Les SceneNodes peuvent être attacher à d'autres SceneNodes qui vous permet de créer une hiérarchie de nodes. Nous verrons l'utilisation avancée des SceneNodes dans un prochain tutoriel.

Un concept majeur à noter est que la position des SceneNodes est toujours relative à son parent, et chaque SceneManager contient un nœud maître auquel d'autres SceneNodes sont attachés.


Votre première application Ogre modifier

Maintenant, retournez au code crée précédemment et cherchez la fonction _createScene dans la classe TutorialApplication. Nous manipulerons seulement le contenu de cette fonction dans ce tutoriel. La première chose que nous voulons faire est de placer une lumière ambiante dans la scène afin de voir ce qu'il s'y passe. Pour faire ceci, nous allons appeler la fonction setAmbientLight et nous allons spécifier la couleur que nous désirons. Notez que le constructeur de ColourValue prend des valeurs pour rouge, vert et bleu entre 0 et 1. Ajoutez ces lignes à _createScene:

sceneManager = self.sceneManager
sceneManager.ambientLight = ogre.ColourValue (1, 1, 1)

La seconde chose à faire est de créer une entité. Nous allons faire ceci par l'appel de la méthode createEntity du SceneManager :

ent1 = sceneManager.createEntity ("Robot", "robot.mesh")

Certaines questions peuvent surgir. La première étant, d'où vient self.sceneManager et qu'appelons avec createEntity ? La variable self.sceneManager contient l'objet !SceneManager actuel (c'est donné par la classe !SampleFramework.Application ). Le premier paramètre de createEntity est le nom de l'entité crée. Toutes les entités ont besoin d'un nom UNIQUE. Vous aurez une erreur si vous tentez de créer deux entités portant le même nom. Le paramètre "robot.mesh" spécifie le mesh (modèle) qui sera utilisé pour l'entité. Ensuite, le mesh que nous voulons utiliser a été pré-chargé pour nous par la classe !SampleFramework.Application.

Maintenant que nous avons crée l'entité, nous devons l'attacher à un SceneNode. Chaque SceneManager a un SceneNode maître, nous allons créer un SceneNode enfant :

node1 = sceneManager.rootSceneNode.createChildSceneNode ("RobotNode")

Ceci appelle la méthode 'createChildSceneNode' du SceneNode maître (root). Le paramètre de createChildSceneNode est le nom du !SceneNode qui vient d'être crée. Comme les classes entités, deux SceneNodes ne peuvent avoir le même nom.

Enfin, nous devons attacher l'entité au SceneNode pour que le Robot ait une localisation et puisse être affiché :

node1.attachObject (ent1)

Et voila ! Démarrez l'application. Vous devriez voir un robot sur votre écran.


Coordonnées et vecteurs modifier

Avant d'aller plus loin, nous devons parler à propos des coordonnées de l'écran et des objets vecteurs (Vector) de Ogre. Ogre (comme la plupart des moteurs graphiques) utilise les axes x et z pour un plan horizontal, et l'axe y pour l'axe vertical. Comme vous regardez votre écran maintenant, l'axe x devrait aller du côté gauche au côté droit de votre écran, avec le côté droit qui est la direction x positive. L'axe y devrait aller du bas en haut de votre écran, avec le haut qui est la direction y positive. L'axe z devrait aller à l'intérieur puis dehors de votre écran, avec le dehors de l'écran qui est la direction z positive.

Remarquez comment notre robot fait face le long de la direction positive x? C'est une propriété de l'objet lui-même, et comment c'était désigné. Ogre ne fait pas d'hypothèses à propos de l'orientation de vos modèles. Chaque objet que vous chargez devrait avoir une "direction de départ" différent, qui y est confronté.

Ogre utilise la classe Vector pour représenter toutes les positions et directions (il n'y a pas de classe Point). Il y a des vecteurs définis pour 2 (Vector2), 3 (Vector3) et 4 (Vector4) dimensions, avec Vector3 qui est le plus utilisé. Si vous n'êtes pas familiarisés avec les vecteurs, je vous suggère de porter un œil dessus avant de faire quelque chose de sérieux avec Ogre. Les maths derrière les vecteurs deviendront très utiles quand vous commencerez à faire des programmes complexes.


Ajouter d'autres objets modifier

Maintenant que vous comprenez comment le système de coordonnées marche, nous pouvons revenir à notre code. Dans les cinq lignes que nous avons écrites, nulle part nous ne spécifions la position exacte où nous voulons que notre robot apparaisse. Une grande majorité des fonctions dans Ogre ont des paramètres par défaut pour celles-ci. Par exemple, la fonction SceneNode.createChildSceneNode dans Ogre a ces trois paramètres: le nom de la SceneNode, la position de la !SceneNode, et la rotation initiale (orientation) que la SceneNode a. La position, comme vous pouvez le voir, a été mise pour nous sur les coordonnées (0, 0, 0). Crééons une autre SceneNode, mais cette fois nous spécifierons l'endroit de départ pour qu'il ne soit pas le même que l'original:

ent2 = sceneManager.createEntity ("Robot2", "robot.mesh")
node2 = sceneManager.rootSceneNode.createChildSceneNode ("RobotNode2", ogre.Vector3 (50, 0, 0))
node2.attachObject (ent2)

Ça devrait avoir l'air familial. Nous avons fait exactement la même chose qu'avant, avec deux exceptions. Premièrement, nous avons nommé l'entité et la !SceneNode en quelque chose de légèrement différent. La deuxième chose que nous avons fait est de spécifier que la position de départ sera 50 unités plus loin dans la direction x que la !SceneNode parente (souvenez-vous que toutes les positions de !SceneNode sont relatives à leurs parents). Lancez votre application? Maintenant il y a deux robots, un devant l'autre.


Vector et ColourValue modifier

Comme vous pouvez l'imaginer, certaines classes sont utilisés encore et encore pour beaucoup de programmes utilisant ogre. Vector3 et ColourValue sont deux de ceux-là. Pour vous rendre la vie plus facile, nous avons fait ça pour que quand vous avez besoin d'utiliser un vecteur ou une valeur de couleur, vous pouvez utiliser un tuple à la place. Ceci signifie qu'au lieu de taper ogre.Vector3(0, 50, 0), vous pouvez simplement taper (0, 50, 0). À la place de ogre.ColourValue(1, 1, 1) vous pouvez taper (1, 1, 1) maintenant. Appliquons ce changement dans la fonction _createScene. Votre code devrait maintenant ressembler à ça:

sceneManager = self.sceneManager
sceneManager.ambientLight = (1, 1, 1)
        
ent1 = sceneManager.createEntity ("Robot", "robot.mesh")
node1 = sceneManager.rootSceneNode.createChildSceneNode ("RobotNode")
node1.attachObject (ent1)
        
ent2 = sceneManager.createEntity ("Robot2", "robot.mesh")
node2 = sceneManager.rootSceneNode.createChildSceneNode ("RobotNode2", (50, 0, 0))
node2.attachObject (ent2)

Les entités plus en profondeur modifier

La classe Entity est très étendue, et je ne vais pas vous expliquer comment utiliser toutes les portions de l'objet ici...juste assez pour que vous commenciez. Il y a quelques fonctions immédiatement utiles dans Entity que je voudrais vous apprendre.

La première est l'attribut "visible". Vous pouvez mettre une Entity sur visible ou non en changeant simplement cet attribut sur True ou False (vrai ou faux). Si vous avez besoin de cacher une entité, mais de l'afficher plus tard, alors appelez cette fonction au lieu de détruire l'entité et de la recréer plus tard. Notez que vous n'avez pas besoin de "mettre ensemble" les entités. Seul une copie du modèle et de la texture d'un objet sont chargés dans la mémoire, donc vous ne sauvegardez pas beaucoup plus en essayant de les rassembler. La seule chose que vous sauvegardez vraiment est le prix de la création et de la destruction de l'objet Entity lui-même, qui est vraiment lent.

L'attribut "name" retourne le nom d'une entité (notez que c'est en lecture seule, vous ne pouvez pas changer le nom d'une entité après l'avoir créée). L'attribut "parentSceneNode" retourne la !SceneNode à laquelle l'entité est attachée.


SceneNodes plus détaillé modifier

La classe SceneNodes est très complexe. Il y a un grand nombre de choses qui peuvent utiliser un SceneNode, alors nous allons seulement couvrir les plus utilisées.

Vous pouvez obtenir et modifier la position d'un SceneNode en utilisant l'attribut "position" (position toujours relative au SceneNode parent). Vous pouvez déplacer l'objet relativement par rapport à sa position actuelle en utilisant la méthode translate.

SceneNodes ne modifie pas seulement la position, mais gère également le changement d'échelle et la rotation de l'objet. Vous pouvez définir l'échelle d'un objet avec l'attribut scale, et la changer avec la fonction scale. Vous pouvez utilisez les fonctions yaw, roll, and pitch pour tourner l'objet. Vous pouvez utiliser la fonction resetOrientation pour annuler toutes les rotations effectuées sur un objet. Vous pouvez également utiliser l'attribut "orientation" pour obtenir et modifier l'orientation d'un objet, et la fonction rotate pour effectuer des rotations avancées. We will not be covering Quaternions until a much later tutorial though.

You have already seen the attachObject function. These related functions and attributes are also useful if you are looking to manipulate the objects that are attached to a SceneNode : the "numAttachedObjects" attribute, getAttachedObject function (there are multiple versions of this function), detachObject function (also multiple versions), detachAllObjects function. There are also a whole set of functions for dealing with parent and child SceneNodes as well.

Since all positions/translating is done relative to the parent SceneNode, we can make two SceneNodes move together very easily. We currently have this code in application :

ent1 = sceneManager.createEntity ("Robot", "robot.mesh")
node1 = sceneManager.rootSceneNode.createChildSceneNode ("RobotNode")
node1.attachObject (ent1)
        
ent2 = sceneManager.createEntity ("Robot2", "robot.mesh")
node2 = sceneManager.rootSceneNode.createChildSceneNode ("RobotNode2", (50, 0, 0))
node2.attachObject (ent2)

If we change the 6th line from this:

node2 = sceneManager.rootSceneNode.createChildSceneNode ("RobotNode2", (50, 0, 0))

To this:

node2 = node1.createChildSceneNode ("RobotNode2", (50, 0, 0))

Then we have made RobotNode2 a child of RobotNode. Moving node1 will move node2 along with it, but moving node2 will not affect node1. For example this code would move only RobotNode2:

node2.translate ((0, 50, 0));

The following code would move RobotNode, and since RobotNode2 is a child of RobotNode, RobotNode2 would be moved as well:

node1.translate ((0, 50, 0));

If you are having trouble with this, the easiest thing to do is to start at the root SceneNode and go downwards. Lets say (as in this case), we started node1 at (0, 0, 0) and translated it by (25, 0, 0), thus node1's position is (25, 0, 0) relative to its parent. node2 started at (50, 0, 0) and we translated it by (10, 0, 10), so its new position is (60, 0, 10) relative to it's parent.

Now lets figure out where these things really are. Start at the root SceneNode. It's position is always (0, 0, 0). Now, node1's position is (root + node1): (0, 0, 0) + (25, 0, 0) = (25, 0, 0). Not surprising. Now, node2 is a child of node1, so its position is (root + node1 + node2): (0, 0, 0) + (25, 0, 0) + (60, 0, 10) = (85, 0, 10). This is just an example to explain how to think about SceneNode position inheritance. You will rarely ever need to calculate the absolute position of your nodes.

Lastly, note that you can get both SceneNodes and Entities by their name by calling getSceneNode and getEntity methods of the SceneManager, so you don't have to keep a pointer to every SceneNode you create. You should hang on to the ones you use often though.


Conclusions modifier

By this point you should have a very basic grasp of the SceneManager, SceneNode, and Entity classes. You do not have to be familiar with all of the functions that I have given reference to. Since these are the most basic objects, we will be using them very often. You will get more familiar with them after working through the next few tutorials.


Complete Source Code Example modifier

#!/usr/bin/env python 
# This code is Public Domain. 
"""Python-Ogre Basic Tutorial 01: The SceneNode, Entity, and SceneManager constructs.""" 

import ogre.renderer.OGRE as ogre 
import SampleFramework as sf 

class TutorialApplication (sf.Application): 
   """Application class.""" 
   
   def _createScene (self):        
       # Setup the ambient light. 
       sceneManager = self.sceneManager 
       sceneManager.ambientLight = (1.0, 1.0, 1.0) 
       
       # Setup a mesh entity and attach it to the root scene node. 
       ent1 = sceneManager.createEntity ('Robot', 'robot.mesh') 
       node1 = sceneManager.rootSceneNode.createChildSceneNode ('RobotNode') 
       node1.attachObject (ent1) 
       
       # Setup a second mesh entity as a child node. 
       ent2 = sceneManager.createEntity ('Robot2', 'robot.mesh') 
       node2 = node1.createChildSceneNode ('RobotNode2', (50, 0, 0)) 
       node2.attachObject (ent2) 

if __name__ == '__main__': 
   ta = TutorialApplication () 
   ta.go ()

Notes modifier

SampleFramework is used and while it isn't as flexible as a full game might require it is a great resource for learning Python-Ogre.

Proceed to Basic Tutorial 2 Cameras, Lights, and Shadows