retour

Petite introduction à Panda3D

Je vais considérer que non seulement vous avez une idée de ce que sont python et Panda3D, mais qu'en plus vous les avez même installés. Si, arrivé là, vous n'avez encore rien fait de ce moteur 3D, et que vous voulez en découvrir les bases, cette page est faite pour vous. Elle reprend le code de l'introduction officielle.

Démarrer Panda3D :

import direct.directbase.DirectStart
run()

direct.directbase.DirectStart charge plein de modules Panda3D et appelle une fenêtre de base. Si tout est installé correctement, le programme ci-dessus affiche une fenêtre à fond gris, avec rien dedans.

Scene Graph et Nodes :

Objets, lumières, caméra, tout est stocké dans le Scene Graph. Le Scene Graph est un arbre composé de nœuds, les Nodes. A chaque Node est associé un NodePath, qui permet de pointer sur le Node dans le Scene Graph. Les deux Nodes de niveau maximum (les plus hauts dans le Scene Graph) sont render, et render2d. Pour qu'un objet s'affiche dans une scene, il faut en faire un fils (direct ou non) de render (ce qui se fait avec la fonction reparentTo).

Models et Actors :

Un model est un objet fixe. Il peut être déplacé, tourné, agrandi, mais ne présente aucune animation propre, au contraire de l'Actor. Models et Actors peuvent être de n'importe quelle taille.

Model :

Le premier model que nous allons importer dans notre scène est l'environnement :

import direct.directbase.DirectStart

environ = loader.loadModel("models/environment")
environ.reparentTo(render)
environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)

run()

Par défaut, Panda3D met à notre disposition un trackball qui permet de manipuler la caméra, testez-le en appuyant sur le premier, le second, le troisième bouton de la souris et en la déplaçant. Nous verrons plus bas comment donner des instructions à la caméra dans le code. Reprenons le code :

environ = loader.loadModel("models/environment")

Cette ligne nous permet de charger le modèle. Nous le chargeons depuis les modèles installés par défaut avec Panda3D. Les modèles pour Panda3D sont dans le format 'egg', un format texte de description d'objets et d'animations. Nous verrons plus bas comment exporter du egg depuis Blender. Si vous avez un fichier egg nommé 'model.egg' dans le répertoire où se trouve le script python, vous pouvez l'appeler indifféremment par :

environ = loader.loadModel("model.egg")
environ = loader.loadModel("model")

Vous pourrez également tomber sur des fichiers en 'egg.pz' : ce sont des fichiers egg qui ont été compressés avec une variante de gzip propre à Panda3D. Inutile de chercher à compresser ou décompresser ces fichiers avec gzip, il vous faudra plutôt utiliser les utilitaires de Panda3D pzip et punzip pour ce faire. 'environ' est de type libpanda.NodePath

environ.reparentTo(render)

Comme évoqué plus haut, c'est l'instruction qui attache le Node pointé par environ au Node pointé par render (environ et render sont des NodePath), et qui en permet donc l'affichage.

environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)

Comme ça me semble clair, je ne vais pas m'étendre outre mesure : ici, on change l'échelle et la position de notre modèle, en prenant en compte le fait que dans Panda3D, les axes X, Y, et Z forment un trièdre "main droite", le pouce pointant vers la droite, l'index vers l'avant, et le majeur vers le haut.

Actor :

Un Actor diffère d'un Model par sa possibilité de suivre un cycle d'animation, comme va nous le démontrer le panda :

import direct.directbase.DirectStart
from direct.task import Task
from direct.actor import Actor
import math


environ = loader.loadModel("models/environment")
environ.reparentTo(render)
environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)

pandaActor = Actor.Actor("models/panda-model",{"walk":"models/panda-walk4"})
pandaActor.setScale(0.005,0.005,0.005)
pandaActor.reparentTo(render)
pandaActor.loop("walk")

run()

Notez la présence de l'instruction 'loop', qui permet de charger un cycle d'animation de notre Actor. Evidemment, pour le moment, notre panda piétine un peu : personne ne lui a demandé de bouger. C'est l'étape suivante :

import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task import Task
from direct.actor import Actor
from direct.interval.IntervalGlobal import *
import math

environ = loader.loadModel("models/environment")
environ.reparentTo(render)
environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)

pandaActor = Actor.Actor("models/panda-model",{"walk":"models/panda-walk4"})
pandaActor.setScale(0.005,0.005,0.005)
pandaActor.reparentTo(render)
pandaActor.loop("walk")

#Create the four lerp intervals needed to walk back and forth
pandaPosInterval1= pandaActor.posInterval(13,Point3(0,-10,0), startPos=Point3(0,10,0))
pandaPosInterval2= pandaActor.posInterval(13,Point3(0,10,0), startPos=Point3(0,-10,0))
pandaHprInterval1= pandaActor.hprInterval(3,Point3(180,0,0), startHpr=Point3(0,0,0))
pandaHprInterval2= pandaActor.hprInterval(3,Point3(0,0,0), startHpr=Point3(180,0,0))

#Create and play the sequence that coordinates the intervals
pandaPace = Sequence(pandaPosInterval1, pandaHprInterval1,
	pandaPosInterval2, pandaHprInterval2, name = "pandaPace")
pandaPace.loop()

run()

Notez le from direct.interval.IntervalGlobal import * qui vient s'ajouter à la liste des modules à importer.

Task :

Une task (tâche) est une fonction appelée à chaque frame. Pour l'exemple, on va créer une fonction qui fait tourner la caméra, et en faire une task en l'ajoutant au Task Manager (taskMgr de son petit nom) :

import direct.directbase.DirectStart
from pandac.PandaModules import *

from direct.task import Task
from direct.actor import Actor
from direct.interval.IntervalGlobal import *
import math

environ = loader.loadModel("models/environment")
environ.reparentTo(render)
environ.setScale(0.25,0.25,0.25)
environ.setPos(-8,42,0)

pandaActor = Actor.Actor("models/panda-model",{"walk":"models/panda-walk4"})
pandaActor.setScale(0.005,0.005,0.005)
pandaActor.reparentTo(render)
pandaActor.loop("walk")

#Create the four lerp intervals needed to walk back and forth
pandaPosInterval1= pandaActor.posInterval(13,Point3(0,-10,0), startPos=Point3(0,10,0))
pandaPosInterval2= pandaActor.posInterval(13,Point3(0,10,0), startPos=Point3(0,-10,0))
pandaHprInterval1= pandaActor.hprInterval(3,Point3(180,0,0), startHpr=Point3(0,0,0))
pandaHprInterval2= pandaActor.hprInterval(3,Point3(0,0,0), startHpr=Point3(180,0,0))

#Create and play the sequence that coordinates the intervals
pandaPace = Sequence(pandaPosInterval1, pandaHprInterval1,
  pandaPosInterval2, pandaHprInterval2, name = "pandaPace")
pandaPace.loop()

def SpinCameraTask(task):
	angledegrees = task.time * 6.0
	angleradians = angledegrees * (math.pi / 180.0)
	base.camera.setPos(20*math.sin(angleradians),-20.0*math.cos(angleradians),3)
	base.camera.setHpr(angledegrees, 0, 0)
	return Task.cont

taskMgr.add(SpinCameraTask, "SpinCameraTask")

run()



accueil