Tutorial Di PyOgre Per Principianti 1
Versione tradotta dagli utenti di Python-it.org. La versione originale è disponibile qui: PyOgre Beginner Tutorial 1
Tutorial Di PyOgre Per Principianti 1: i costrutti SceneNode, Entity e SceneManager
Versione originale di Clay Culver
Nota: questo tutorial è stato scritto per PyOgre 1.0.5, in cui è noto funzionare. Se non fosse la versione corrente di PyOgre e qualcosa non funziona, oppure non capisci qualcosa contenuto qui, ti prego di postare le tue domande nel PyOgre Forum.
Prerequisiti
Questo tutorial assume che tu abbia conoscenze di programmazione in Python e tu abbia già installato PyOgre. Non è presupposta alcuna conoscenza di Ogre per questo tutorial.
Introduzione
In questo tutorial ti presenterò i più elementari costrutti di Ogre: SceneManager, SceneNode e gli oggetti Entity. Non tratteremo una larga parte di codice; mi focalizzerò invece nei concetti generali affinché tu cominci ad imparare Ogre.
Progredendo lungo il tutorial dovresti lentamente aggiungere del codice al tuo progetto osservando i risultati durante l'aggiunta. Non c’è altro sostituto, nella programmazione attuale, per familiarizzare con questi concetti. Resisti all’impulso di leggere soltanto. Se hai dei problemi puoi scaricare il sorgente qui.
Nota che questo programma richiede che tu usi il file “SampleFramework.py”. Puoi reperirlo nella directory “demos/”, oppure, se hai difficoltà a trovarlo, puoi scaricarlo da qui. Dovrai anche assicurarti di avere i file “resources.cfg” e “media.cfg” nella stessa directory del tuo codice sorgente, e che il file “resources.cfg” punti al corretto percorso della tua directory “Media”. Generalmente, se vuoi un “quick start” per fare questo, la cosa migliore da fare è creare il tuo file sorgente nella stessa directory dei demo di pyogre.
Iniziare
Codice base
Useremo un codice pre-costruito per questo tutorial. Tu dovresti ignorare tutto il codice eccetto quello che tra poco aggiungeremo al metodo _createScene. In un prossimo tutorial andremo in profondità spiegando come funziona un’applicazione Ogre, ma per ora cominceremo dal livello più elementare. Crea un file sorgente Python chiamato “basic_1.py” e aggiungici il codice seguente:
1 from pyogre import ogre
2 import SampleFramework
3
4 class TutorialApplication(SampleFramework.Application):
5 def _createScene(self):
6 pass
7
8 if __name__ == '__main__':
9 ta = TutorialApplication()
10 ta.go()
Una volta che il programma è in esecuzione, usa i tasti WASD e il mouse per guardarti intorno. Il tasto Escape esce dal programma. Comunque, dato che non abbiamo inserito alcun oggetto alla scena, non ci sarà niente da vedere,
from pyogre import …
Nel codice sopra, abbiamo importato il modulo ogre dal package pyogre. E’ molto importante notare che non abbiamo importato tutto dal modulo. E’ sicuro usare questo codice:
1 from pyogre import *
Comunque, a meno che tu non stia realmente utilizzando tutto in PyOgre, raccomando di non farlo. PyOgre è un modulo molto grande, ed ogni suo componente richiede tempo e memoria per essere caricato. Di solito non è un problema per la maggior parte delle librerie python, ma è difficile evidenziare esattamente quando grande sia PyOgre. Il wrapper per PyOgre misura oltre 200.000 righe di codice C++, fatte funzionare da oltre 4.000 righe di codice Python. Senza dubbio python gestisce il caricamento e l’utilizzo dei moduli in modo veramente efficiente e non ti dovresti accorgere molto dell’impatto di ciò. Ciò nonostante, se non utilizzi CEGUI, non c’è ragione di includerlo nell’istruzione import.
Come funziona Ogre
Un vasto argomento. Cominceremo con le SceneManager per arrivare ai MovableObjects e gli SceneNodes. Queste tre classi sono fondamentali nel costruire i blocchi di un applicazione Ogre.
Le basi di SceneManager
Tutto ciò che appare nello schermo è gestito dalla SceneManager (immagina questo). Quando piazzi degli oggetti nella scena, SceneManager è la classe che tiene traccia delle loro locazioni. Quando crei delle Cameras per vedere la scena (che tratteremo in un prossimo tutorial) la SceneManager tiene traccia di esse. Quando tu crei aerei, tabelloni, luci…e così via, la SceneManager tiene traccia di questi oggetti.
Esistono sono molti tipi di SceneManager. Ci sono SceneManager che renderizzano il terreno, c'è una SceneManager per il rendering delle mappe BSP, e così via. Puoi vedere i vari tipi di SceneManager listati qui. Tratteremo altri aspetti delle SceneManager progredendo nei tutorials.
Le basi dei MovableObjects
Un MovableObject è qualsiasi cosa che può essere posizionata in una scena e spostata al suo interno.
Una Entity è uno dei tipi di oggetti che tu puoi disegnare in una scena (ed è una sottoclasse di MovableObject). Puoi pensare ad una Entity come a qualcosa che può essere rappresentato da una mesh 3D. Un robot è una Entity, un pesce è una Entity, il terreno dove i tuoi characters camminano è una entity molto molto grande. Cose come Lights, Bilboards, Particles, Cameras, ecc. non sono Entity, ma sono MovableObjects.
Una cosa da notare di Ogre è che separa gli oggetti disegnabili dalla loro locazione e orientazione. Ciò significa che tu non puoi piazzare direttamente una Entity in una scena. Devi invece collegare la Entity ad un oggetto SceneNode, e questo SceneNode contiene l’informazione sulla posizione e orientazione.
Le basi dello SceneNode
Come già menzionato, gli oggetti SceneNode tengono traccia della locazione e orientazione di tutti gli oggetti collegati a loro. Quando tu crei una Entity, questa non viene disegnata sulla scena finché tu non la colleghi a uno SceneNode. Analogamente, uno SceneNode non è un oggetto che è mostrato sullo schermo. Solo quando tu crei uno SceneNode e lo colleghi ad una Entity (o ad altri oggetti) si ha un qualcosa effettivamente mostrato sullo schermo.
Gli oggetti SceneNode possono avere qualsiasi numero di oggetti collegati a essi. Diciamo che tu hai un character che cammina sulla scena e vuoi che lui generi una luce intorno a sé. Il modo per fare questo è innanzitutto creare uno SceneNode, quindi creare una Entity per il character e attaccarlo allo SceneNode. Quindi crea un oggetto Light e collegalo allo SceneNode. Gli SceneNode possono anche essere collegati ad altri SceneNode che ti permettono di creare intere gerarchie di noti. Tratteremo usi più avanzati dei collegamenti di SceneNode in un futuro tutorial.
Uno dei principali concetti da notare a proposito di SceneNode è che una posizione di SceneNode è sempre relativa al suo SceneNode genitore, e ciascuna SceneManager contiene un nodo radice a cui gli altri SceneNodes sono collegati.
La tua prima applicazione Ogre
Torniamo adesso al codice che abbiamo creato prima. Trova la funzione TutorialApplication_createScene. Manipoleremo soltanto i contenuti di questa funzione, in questo tutorial. La prima cosa che vogliamo fare è importare la luce ambientale della scena in modo da poter vedere quello che stiamo facendo. Facciamo questo chiamando la funzione setAmbientLight e specificando quale colore vogliamo. Nota che il costruttore ColorValue si aspetta valori per il rosso, verde e blue nell’intervallo compreso tra 0 e 1. Aggiungi questa riga a createScene:
1 sceneManager = self.sceneManager
2 sceneManager.ambientLight = ogre.ColourValue(1, 1, 1)
La prossima cosa che dobbiamo fare è creare una Entity. Lo facciamo chiamando il metodo createEntity di SceneManager.
1 ent1 = sceneManager.createEntity("Robot", "robot.mesh")
Ok dovrebbero emergere diverse domande. Innanzitutto, da dove è venuto self.sceneManager e con cosa stiamo chiamando createEntity? La variabile self.sceneManager contiene l’oggetto SceneManager corrente (la classe SampleFramework.Application fa questo per noi). Il primo parametro passato a createEntity è il nome della Entity che stiamo creando. Tutte le Entity devono avere un nome unico. Causerai un errore se provi a creare due Entity con lo stesso nome. Ancora una volta, la mesh che stiamo usando è stata precaricata per noi dalla classe SampleFramework.Application.
Adesso che abbiamo creato la Entity, dobbiamo creare uno SceneNode a cui collegarla. Siccome ogni SceneManager ha uno SceneNode radice, andiamo a creare un figlio di questo nodo:
1 node1 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")
Questa istruzione chiama il metodo createChildSceneNode della radice SceneNode. Il parametro passato a createChildSceneNode è il nome dello SceneNode che abbiamo creato. Come per la classe Entity, non ci possono essere due oggetti SceneNode con lo stesso nome.
Infine, dobbiamo collegare la Entity allo SceneNode, in modo che il Robot ha una locazione dove può essere disegnato.
1 node1.attachObject(ent1)
Ecco fatto! Esegui la tua applicazione. Dovresti vedere un robot, in piedi, sullo schermo.
Coordinate e Vettori
Prima di andare oltre, dobbiamo parlare delle coordinate dello schermo e degli oggetti Vector di Ogre. Ogre (come molti motori grafici) usa gli assi x e z come piano orizzontale, e l’asse y come asse verticale. Se guardi il tuo schermo, l’asse x è quello che va dal lato sinistro al lato destro nel tuo monitor, con il lato destro che corrisponde alla direzione x positiva. L’asse z è quello che va dentro e fuori dallo schermo, con il fuori dello schermo che corrisponde alla direzione z positiva.
Hai notato che il nostro Robot è rivolto verso la direzione x positiva? Questa è una proprietà della mesh stessa, e di come è stata progettata. Ogre non fa assunzioni su come tu orienti i tuoi modelli. Ogni mesh che carichi può avere una differente direzione di inizio a cui è rivolta.
Ogre utilizza la classe Vector per rappresentare la posizione ma anche la direzione (non c’è la classe Point). Ci sono vettori definiti per 2 (Vector2), 3 (Vector3), e 4 (Vector4) dimensioni, di cui quello più comunemente usato è Vector3. Se non hai familiarità con i Vettori, ti suggerisco di chiarirteli prima di fare qualcosa di serio con Ogre. La matematica dietro i Vector diventerà molto utile quando comincerai a lavorare in programmi complessi.
Aggiungere un altro oggetto
Ora che sai come funzionano i sistemi di coordinate, possiamo ritornare al nostro codice. Nelle cinque linee che abbiamo scritto, non abbiamo specificato da nessuna parte la posizione esatta in cui vogliamo che il nostro robot appaia. La grande maggioranza delle funzioni in Ogre hanno parametri di default. Ad esempio, il metodo SceneNode.createChildSceneNode in Ogre ha tre parametri: il nome dello SceneNode, la posizione dello SceneNode e la rotazione iniziale (orientamento) a cui lo SceneNode è rivolto. La posizione, come puoi vedere, è stata messa per noi alle coordinate (0, 0, 0). Creiamo un altro SceneNode, ma questa volta specificheremo la posizione iniziale in modo da fare qualcosa di diverso dall’originale:
1 ent2 = sceneManager.createEntity("Robot2", "robot.mesh")
2 node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", ogre.Vector3(50, 0, 0))
3 node2.attachObject(ent2)
Questo dovrebbe apparire familiare. Abbiamo fatto la stessa cosa di prima, con due eccezioni. Prima di tutto, abbiamo chiamato la Entity e lo SceneNode in un modo leggermente diverso. La seconda cosa che abbiamo fatto è che abbiamo specificato che la posizione iniziale sarà 50 unità di distanza, lungo la direzione x, dalla radice SceneNode (ricorda che tutte le posizioni di SceneNode sono relative ai suoi nodi genitori). Compila ed esegui la demo. Ora ci sono due robots l’uno accanto all’altro.
Vettori e ColourValue
Come puoi immaginare, certe classi sono utilizzate più e più volte per una bella fetta di Ogre. Vector3 e ColourValue sono due di queste. Per renderti la vita più facile, abbiamo fatto in modo tale che ogni volta che hai bisogno di usare un Vector o un ColourValue, puoi invece usare una tupla. Ciò significa che invece di scrivere ogre.Vector3(0,50,0), puoi semplicemente scrivere (0, 0, 0). Invece di ogre.ColourValue(1, 1, 1) puoi scrivere (1, 1, 1) adesso. Fai questo cambiamento nella funzione _createScene per usare questa funzionalità. Il tuo codice dovrebbe ora assomigliare a questo:
1 sceneManager = self.sceneManager
2
3 sceneManager.ambientLight = (1, 1, 1)
4
5 ent1 = sceneManager.createEntity("Robot", "robot.mesh")
6 node1 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")
7 node1.attachObject(ent1)
8
9 ent2 = sceneManager.createEntity("Robot2", "robot.mesh")
10 node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2")
11 node2.attachObject(ent2)
Entity più in profondità
La classe Entity è molto ampia, e non tratterò come usare ogni porzione dell’oggetto, qui…ma giusto il necessario per farti iniziare. Ci sono pochi metodi immediatamente utili nella Entity che vorrei puntualizzare.
Il primo è l’attributo “visible”. Puoi impostare che la Entity sia visibile o no semplicemente settando questo attributo a True o False. Se ti serve nascondere una Entity, ma mostrarla in seguito, chiama questo metodo invece di distruggere la Entity e ricrearla dopo. Nota che non hai bisogno di [
"pool" Entities up]. Solo una copia di qualunque mesh e texture è caricata in memoria, così tu non salvi molta roba provando a [
pool them]. La sola cosa che tu realmente salvi sono i costi di creazione e distruzione dell’oggetto Entity stesso, che è relativamente basso [
The only thing you really save is the creation and destruction costs for the Entity object itself, which is relatively low].
L’attributo “name” restituisce il nome di una Entity (nota che è di sola lettura, non puoi cambiare il nome di una Entity dopo averla creata). L’attributo “parentSceneNode” restituisce lo SceneNode a cui la Entity è collegata.
Gli SceneNode più in profondità
La classe SceneNode è molto complessa. Ci sono un sacco di cose che si possono fare con uno SceneNode, quindi noi tratteremo soltanto alcuni dei più utili.
Puoi ottenere e impostare la posizione di un oggetto SceneNode utilizzando l’attributo “position” (sempre relativo allo SceneNode genitore). Puoi muovere il relativo oggetto alla sua posizione corrente utilizzando il metodo traslate.
Gli oggetti SceneNode non soltanto impostano la posizione, ma gestiscono anche la scala e la rotazione dell’oggetto. Puoi impostare la scala di un oggetto con l’attributo “scale”, e scalarlo effettivamente usando la funzione scaleBy (vedi la sezione Scale più avanti per ulteriori informazioni). Puoi usare le funzioni yaw, roll, e pitch per ruotare gli oggetti. Puoi usare resetOrientation per annullare tutte le rotazioni fatte all’oggetto. Puoi usare l’attributo “orientation” per ottenere ed impostare l’orientazione, e la funzione rotate per rotazioni più avanzate. Ci occuperemo però dei Quaternioni però soltanto in un tutorial molto più avanti.
Hai già visto la funzione attachObject. Queste funzioni e attributi collegati sono anch’essi utili se hai intenzione di manipolare gli oggetti che sono attaccati ad uno SceneNode: l’attributo “numAttachedObjects”, le funzioni getAttachedObject (ci sono versioni multiple di questa funzione), detachObject (anch’essa versioni multiple) e detachAllObjects. C’è anche un intero insieme di funzioni per occuparsi degli SceneNode padri e figli.
Dato che tutte le posizioni e le traslazioni sono relative allo SceneNode genitore, possiamo far muovere due SceneNode insieme molto facilmente. Ora abbiamo questo codice nell’applicazione:
1 ent1 = sceneManager.createEntity("Robot", "robot.mesh")
2 node1 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")
3 node1.attachObject(ent1) ent2 = sceneManager.createEntity("Robot2", "robot.mesh")
4
5 ent2 = sceneManager.createEntity("Robot2", "robot.mesh")
6 node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", ogre.Vector3(50, 0, 0))
7 node2.attachObject(ent2)
Se noi cambiamo la 6a linea da così:
1 node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", ogre.Vector3(50, 0, 0))
A così:
1 node2 = node1.createChildSceneNode("RobotNode2", (50, 0, 0))
Abbiamo reso il RobotNode2 un figlio di RobotNode. Spostando node1 sposteremo node2 insieme ad esso, ma spostare node2 non avrà effetto su node1. Per esempio questo codice sposterebbe soltanto RobotNode2:
1 node2.translate((10, 0, 10));
Il codice seguente sposterebbe RobotNode, e dato che RobotNode2 è un figlio di RobotNode, RobotNode2 si sposterebbe a sua volta:
1 node1.translate((25, 0, 0));
Se hai dei dubbi su questo, la cosa più semplice da fare è partire dallo SceneNode radice e scendere verso il basso. Diciamo che (come in questo caso) abbiamo iniziato con node1 in (0, 0, 0) e lo abbiamo spostato di (25, 0, 0), per cui la posizione del node1 è (25, 0, 0) relativamente al suo nodo genitore. Node2 partiva in (50, 0, 0) e lo abbiamo traslato di (10, 0, 10), dunque la sua posizione è (60, 0, 10), relativamente al suo genitore.
[
Now lets figure out where these things really are]. Comincia dallo SceneNode radice. La sua posizione è sempre (0, 0, 0). Ora, la posizione di node1 è (root + node1): (0, 0, 0) + (25, 0, 0) = (25, 0, 0). Niente di sorprendente. Ora, node2 è un figlio di node1, dunque la sua posizione è (root+ node1 è node2): (0, 0, 0) + (25, 0, 0) + (60, 0, 10) = (85, 0, 10). Questo è giusto un esempio per spiegare come pensare all’ereditarietà della posizione di un oggetto SceneNode. Raramente dovrai calcolare l’assoluta posizione dei tuoi nodi.
Per finire, nota che puoi ottenere sia gli oggetti SceneNode, sia gli oggetti Entity, tramite il loro nome: chiamando i metodi getSceneNode e getEntity della SceneManager, così non hai bisogno di tenere un puntatore ad ogni SceneNode che crei. Dovresti tenerti stretti quelli che usi spesso però.
Cose da provare
Da ora dovresti avere un’infarinatura delle Entity, degli SceneNode e della SceneManager. Ti suggerisco di cominciare col codice sopra e aggiungere e rimuovere Robots dalla scena. Fatto questo, cancella tutti i contenuti del metodo _createScene, e gioca con ciascuno dei seguenti segmenti di codice:
Scale
Puoi scalare (adattare ad un'altra dimensione) la mesh o impostando l’attributo “scale” o chiamando il metodo scaleBy nello SceneNode. Nota che questo è uno dei pochi ambiti in cui abbiamo dovuto significativamente cambiare l’API dall’Ogre C++. Ogre C++ ha una funzione di scalatura (che prende la scala corrente e la moltiplica con dei parametri) e la coppia getScale/setScale (che imposta la scala ad un valore). Noi mappiamo sempre le funzioni get/set ad un attributo dello stesso nome. Quindi in PyOgre, SceneNode.Scale è l’attributo che tu usi per impostare la scala. Abbiamo rinominato la funzione scale di Ogre C++ in scaleBy. Qui c’è un esempio:
1 someNode.scale = 1, 1, 1 # imposta la scala al valore predefinito
2 someNode.scale = 0.5, 1, 2 # imposta la scala a metà in x, doppia in z
1 someNode.scaleBy(1, 2, 3) # lo stesso di 'someNode.scale = someNode.scale * (1, 2, 3)'
Try changing the values in scale and see what you get:
1 sceneManager = self.sceneManager
2 sceneManager.ambientLight = (1, 1, 1)
3
4 ent = sceneManager.createEntity("Robot", "robot.mesh")
5 node = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")
6 node.attachObject(ent)
7 node.scale = (.5, 1, 2)
8
9 ent = sceneManager.createEntity("Robot2", "robot.mesh")
10 node = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", (50, 0, 0))
11 node.attachObject(ent)
12 node.scale = (0.5, 0.2, 1)
13 node.scaleBy(2, 10, 1) # these two lines same as node.scale = (1, 2, 1)
Rotazioni
Puoi ruotare l'oggetto usando i metodi yaw, pitch e roll, usando gli oggetti Degree o Radian. Prova cambiando il valore di Degree e combinando vari transforms:
1 sceneManager = self.sceneManager
2 sceneManager.ambientLight = (1, 1, 1)
3
4 ent1 = sceneManager.createEntity("Robot", "robot.mesh")
5 node1 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode")
6 node1.attachObject(ent1)
7 node1.yaw(ogre.Degree(-90))
8
9 ent2 = sceneManager.createEntity("Robot2", "robot.mesh")
10 node2 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode2", ogre.Vector3(50, 0, 0))
11 node2.attachObject(ent2)
12 node2.pitch(ogre.Degree(-90))
13
14 ent3 = sceneManager.createEntity("Robot3", "robot.mesh")
15 node3 = sceneManager.rootSceneNode.createChildSceneNode("RobotNode3", ogre.Vector3(100, 0, 0))
16 node3.attachObject(ent3)
17 node3.roll(ogre.Degree(-90))
Nota che non puoi chiamare yaw, pich o roll senza usare ogre.Degree o ogre.Radian per specificare quale stai usando.
Conclusioni
A questo punto dovresti avere una comprensione molto basilare delle classi SceneManager, SceneNode e Entity. Non devi acquisire familiarità con tutte le funzioni che ho riportato. Dato che queste sono la maggior parte degli oggetti di base, li useremo molto spesso. Familiarizzerai con essi lavorandoci durante i prossimi tutorials.
Torna a PyOgreTutorial