Stavo attrezzando un piccolo progettino scemo sulle OpenGL (più didattico che altro) e così ho pensato di condividere quelle regole non scritte che servono ad organizzare e gestire un progetto software. Sono piccole “tips”, struttura delle cartelle e file che non devono mai mancare. Presenterò anche altri strumenti, come ho fatto per Python, ma questa volta saranno strumenti “generici”, ovvero utilizzabili con qualunque linguaggio.

Questo perché tenere in piedi un progetto male organizzato è come costruire un castello di carta. Ci vuole una base forte per rendere il più accogliente possibile il progetto per le persone interessate a collaborare, sia per i membri del vostro team (se ne avete uno).

STRUTTURA CARTELLE

Le cartelle vanno organizzate in modo piuttosto coerente. È importante non mischiare tutto in poche cartelle confuse specialmente per progetti destinati a crescere rapidamente. Una struttura minimale è la seguente:

  • src – La cartella src contiene il codice sorgente. Il codice al suo interno può essere organizzato come si desidera in accordo con le convenzioni del linguaggio. L’importante è che contenga solamente il codice del programma/libreria.
  • test – La cartella test contiene il codice sorgente dedicato ai test. Il codice di test serve a provare il corretto funzionamento delle classi sviluppate o di alcuni moduli software oppure possono essere semplicemente test prestazionali.
  • build – La cartella build contiene tutti i file ottenuti come residuo di una compilazione. Possono essere moduli parziali C/C++ (.o), classi Java (.class), file python compilati (.pyc) e così via. La cartella va a sua volta divisa nelle varie configurazioni di compilazione (ad esempio ci potrebbe essere una configurazione Debug con compilati ottimizzati per il debug e una configurazione Release con compilati ottimizzati per le prestazioni).
  • dist – La cartella dist contiene il prodotto finito. Può essere il programma eseguibile, un file .jar, una libreria .so e così via. Tutto quello che sta in dist deve essere pronto per essere eseguito e distribuito.
  • doc – La cartella doc contiene la documentazione dettagliata. Tale documentazione è per lo più la specifica dettagliata delle API la quale è solitamente auto-generata.

FILE MUST

Nella cartella radice sono necessari alcuni file. Necessari è una parola grossa in quanto solitamente il programma funziona benissimo anche senza, ma sono un tocco di classe e di umanità verso chi vuole accedere ai sorgenti della vostra applicazione.

  • Tool di compilazione - Sia Makefile, o Cmake o qualunque altro tool utilizzate… DEVE esserci uno strumento per la configurazione e la compilazione. Molti IDE possono anche auto-generare roba simile.
  • README – Il readme deve dire vita morte e miracoli sull’applicazione. Deve spiegare come può essere compilata  l’applicazione, quali sono le dipendenze, i bug noti, etc..
  • README.src – Per applicazioni il cui README è eccessivamente complesso è bene separare le informazioni per sviluppatori in un file a parte. Il file README.src deve spiegare come è organizzato il codice, quale IDE si è usato (se si è usato), quale librerie di sviluppo servono, quale strumenti si sono usati per generare la documentazione, per i test e per altro. Insomma… tutto ciò che può essere utile per chi è interessato alla modifica del codice.
  • COPYRIGTH - Il file che contiene le informazioni sulla licenza del progetto.
  • CHANGELOG – Il file che contiene la lista dei cambiamenti apportati al programma nel corso della sua storia. Questo file può essere auto-generato da alcuni sistemi di CVS.

DOCUMENTAZIONE

Scrivere a mano la documentazione è sempre l’ultimo dei problemi. Nonostante ci siano molte cose che debbano necessariamente essere scritte a mano, per le API è possibile usare dei tools automatici. Abbiamo visto Epydoc per python. Bene, per tutto il resto c’è Doxygen. Doxygen è un programma di auto-generazione della documentazione utilizzabile per una miriade di linguaggi.

CVS

Il programma per CVS è essenziale se programmate in gruppo ma è molto utile anche se programmate da soli. Vi permette di manipolare il codice con molta disinvoltura navigando fra branch diversi in modo del tutto trasparente.

Potete usare quello che vi pare come CVS anche se io consiglio sempre GIT. Lo trovo molto più veloce ed elastico dei concorrenti. Se volete convincervi potete sempre andare su http://it.whygitisbetterthanx.com/ :D

MESSAGGISTICA

Ebbene si. Se avete un gruppo di persone con cui lavorate insieme non potete prescindere da un sistema rapido e veloce per lo scambio di informazioni, siano esse “OMG! Non funziona una cippa!” o messaggi più tecnici.

A questo punto avete le basi. Non vi resta che seguire questi consigli e lanciarvi nel tempestoso mondo del software libero sperando che il vostro progetto non sia un castello di carte spazzato via dal vento.

 

So che lo state pensando. “Oh no! Un altra GUI per Git! Che dio ci scampi!”. Effettivamente di gui per git ce ne sono a valanghe. Ognuna con i suoi vantaggi e svantaggi. Però Gitg ha qualcosa che te la fa preferire. Innanzitutto è ben integrato con GNOME, secondo ha una velocità impressionante nel caricare repository di enormi dimensioni (dall’immagine potete vedere che carica più di 17.000 commit in meno di un secondo).

E poi è graficamente gradevole con i suoi colori e amenità varie. E questo non guasta mai. :D

Il programma è disponibile nei repo ufficiali di Lucid ma vi consiglio di usare la versione 0.0.6 che invece trovate nei repo di Maverik. Potete prenderla direttamente da la, non ci sono problemi.

Unica pecca il supporto ai repository remoti non ancora completo.

Se vi capita provatelo. Buon sabato.

 

Mi sono ritrovato nella cartella Download del PC questo Pdf. Non mi ricordo dove l’ho preso, in ogni caso voglio rendervi partecipi.

In questo PDF sono elencate in modo sintetico tutte (o quasi) le funzioni di OpenGL e la relativa sintassi. Uno strumento utilissimo per chi programma in OpenGL e, ovviamente, non ricorda la particolare sintassi di una particolare funzione!

Ecco qui di seguito il pratico download! :D

OpenGL4 Quick Reference Card

 

Abbiamo visto come si può disegnare qualsivoglia figura inserendo i vari vertici della figura manualmente. Abbiamo visto anche che prima di inserire un vertice dobbiamo prepararlo impostando, ad esempio, il colore, il vettore normale (vedremo a cosa serve quando parleremo della luce), le texture e via dicendo. Questo può sembrarvi estremamente macchinoso… e lo è: dobbiamo usare circa 3-4 istruzioni per inserire ogni vertice! Un metodo decisamente inadatto per inserire oggetti formati da centinaia di vertici!

Anche volendo disegnare un semplice cubo (quindi con la miseria di 8 vertici) ci rendiamo conto che ogni vertice andrebbe specificato ben 3 volte (uno per ogni faccia a cui appartiene) con inutile spreco di prestazioni!

Ovviamente esiste la soluzione: usare gli array. Tramite gli array possiamo inserire con una manciata di comandi intere liste di vertici. Ma andiamo per passi.

1 – ABILITARE GLI ARRAY

Come gran parte degli stati di OpenGL gli array sono disabilitati di default. Esistono 8 diversi array da abilitare:

  • GL_VERTEX_ARRAY
  • GL_COLOR_ARRAY
  • GL_SECOND_COLOR_ARRAY
  • GL_INDEX_ARRAY
  • GL_NORMAL_ARRAY
  • GL_FOG_COORDINATE_ARRAY
  • GL_TEXTURE_COORDINATE_ARRAY
  • GL_EDGE_FLAG_ARRAY

Ovviamente non ci serviranno tutte adesso e possiamo limitarci, ad esempio, solamente a vertici e colori. Possiamo quindi abilitare gli array che ci interessano con glEnableClientState() come abbiamo visto in precedenza.

2 – INSERIRE DATI NEGLI ARRAY

Una volta che gli array sono abilitati bisogna inserire dei dati al loro interno. Esiste una funzione per ogni tipo di array. Noi però per il momento vedremo solamente le funzioni che ci interessano:

  • glVertexPointer(size, type, stride, pointer) è la funzione che permette di inserire un vettore all’interno dell’array dei vertici. size è semplicemente il numero di coordinate per vertice (può valere solo 2,3 o 4). type indica il tipo con cui sono specificati i vertici (GL_INT_, GL_FLOAT e così via). stride è un valore che indica l’offeset fra vertici consecutivi all’interno del vettore. Questo parametro è solitamente 0 per array che contengono solamente vertici. pointer è invece il puntatore al vettore che contiene i vertici da inserire nell’array.
  • glColorPointer(size, type, stride, pointer) è analoga a sopra ma serve per impostare il vettore dei colori.

3 – UTILIZZARE GLI ARRAY

Esistono diverse funzioni per utilizzare i dati presenti negli array. La prima funzione base è glArrayElement(k). Questa funzione non fa altro che inserire il k-esimo elemento dell’array. Facciamo un esempio. Supponiamo di avere due array vertex e color correttamente inizializzati e inseriti negli array di OpenGL.

A questo punto i due codici sono equivalenti:

glBegin(GL_TRIANGLES)
glArrayElement(2)
glArrayElement(3)
glArrayElement(5)
glEnd()
glBegin(GL_TRIANGLES)
glColor3f(color[2*3], color[2*3 + 1], color[2*3+2])
glVertex2f(color[2*2], color[2*2 + 1])
glColor3f(color[3*3], color[3*3 + 1], color[3*3+2])
glVertex2f(color[3*2], color[3*2 + 1])
glColor3f(color[5*3], color[5*3 + 1], color[5*3+2])
glVertex2f(color[5*2], color[5*2 + 1])
glEnd()

Come potete vedere c’è un bel risparmio. Notate anche che il vertice k-esimo viene colorato con il colore k-esimo.

La seconda funzione che vediamo ci permette di semplificare il tutto ancora di più. Tale funzione è glDrawElements(mode, count, type, indices). Questa funzione è del tutto equivalente a questo codice:

glBegin(mode)
for i in range(count) :
    glArrayElement(indices[i])
glEnd()

Il parametro indices, come avrete notato, è un array di indici. Se indices contiene [0, 4, 6] verranno inseriti in sequenza il vertice 0, 4 e 6 contenuto nell’array di OpenGL. Notate anche che è un errore inserire glDrawElements() all’interno di un blocco glBegin/glEnd.

Una terza funzione è glDrawArray(mode, first, count). Questa funzione è equivalente a:

glBegin(mode)
for i in range(count) :
    glArrayElement(first + i)
glEnd()

La differenza principale con glDrawElements riguarda la scelta degli indici: mentre glDrawElements permette di campionare gli indici in ordine sparso all’interno dell’array di OpenGL, glDrawArray permette solamente di disegnare un gruppo di vertici contiguo.

Per il momento basta così. Vi lascio con un piccolissimo esempio pratico:

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

def init():
    glClearColor(0, 0, 0, 0)
    glShadeModel(GL_SMOOTH)

def test():
    glutInit()

def display():
    glClear(GL_COLOR_BUFFER_BIT)

    glEnableClientState(GL_VERTEX_ARRAY)
    glEnableClientState(GL_COLOR_ARRAY)

    vertici = [25, 25,
        100, 325,
        175, 25,
        175, 325,
        250, 25,
        325, 325]

    colori = [1, 0.2, 0.2,
        0.2, 0.2, 1,
        0.8, 1, 0.2,
        0.75, 0.75, 0.75,
        0.35, 0.35, 0.35,
        0.5, 0.5, 0.5]

    glColorPointer(3, GL_FLOAT, 0, colori)
    glVertexPointer(2, GL_INT, 0, vertici)

    indici = [0, 1, 2, 3, 4, 5]

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, indici)

    glDisableClientState(GL_COLOR_ARRAY)
    glDisableClientState(GL_VERTEX_ARRAY)
    glFlush()

def reshape(w, h):
    glViewport(0, 0, w, h)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluOrtho2D(0, w, 0, h)

if __name__ == '__main__':
    glutInit()
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
    glutInitWindowSize(350, 350)
    glutInitWindowPosition(100, 100)
    glutCreateWindow("Array: Due Triangoli")
    init()
    glutDisplayFunc(display)
    glutReshapeFunc(reshape)
    glutMainLoop()

L’esempio è piuttosto semplice alla luce della spiegazione.

Con questo si conclude la prima parte del corso dedicata ai vertici. Nella prossima vedremo come manipolare la “telecamera” e gli oggetti. Analizzeremo quindi i 3 aspetti fondamentali della visualizzazione: composizione, inquadratura e proiezione.

Vi ricordo che per ulteriori chiarimenti sono sempre disponibile nei commenti, su LQH e se ci sono potete abusare di me all’indirizzo Jabber: thek3nger@jabber.linux.it

 

Passato il week-end posso rimettermi al lavoro per scrivere l’ultima parte del primo capitolo del nostro corso introduttivo alle OpenGL. Nel frattempo però voglio suggerirvi un libro interessante e conciso per capire le meccaniche alla base di ogni sistema grafico tridimensionale.

Il libro non si basa su nessuna libreria ma spiega in modo molto semplice tutti i passi con cui un sistema digitale costruisce, gestisce e disegna su schermo un modello tridimensionale analizzando le varie fasi: modellazione, clipping, proiezione, colore, luce ecc. Un libro molto utile per poter avere una visione di insieme del mondo della grafica 3D mentre si studia una libreria specifica.

Il libro è scritto a quattro mani da Riccardo Scateni, Paolo Cignoni, Claudio Montani e Roberto Scopigno e ha un prezzo di circa 25€.

Se vi interessa il mondo della computer grafica è un libro assolutamente da non perdere.

 

Questa è la penultima lezione del primo grande capitolo di OpenGL: il disegno di poligoni. Oggi parleremo dei “pattern” e di come istruire il motore grafico affinché non utilizzi un colore uniforme per disegnare righe e campiture ma colori soltanto i pixel che decidiamo noi. L’esempio più banale consiste nel voler disegnare una riga tratteggiata o di voler realizzare campiture in half-tone come quella nell’immagine dell’articolo.

Prima di entrare nel dettaglio facciamo un attimo il punto sulla struttura di OpenGL. OpenGL è una macchina a stati. Gli stati attivi influenzano il comportamento di OpenGL in modo sostanziale. Per attivare uno stato si utilizza il comando:

glEnable(state)

e si disabilita con

glDisable(state)

Dove state è l’identificativo di uno dei possibili stati di OpenGL. Non elencherò adesso tutti gli stati possibili di OpenGL perché sono tantissimi ma ve li citerò nel momento del bisogno. Un avvertimento: non tenete tutti gli stati abilitati se non ne avete bisogno perché questo danneggia pesantemente le prestazioni. Se non usate la nebbia (GL_FOG) è inutile che la attivate rendendo più pesante il processo di rendering.

STIPPLE NELLE LINEE

Cominciamo con le linee. Abbiamo visto come tracciare una linea (ad esempio tramite la modalità GL_LINE_STRIP) e come modificare il colore di tale linea (tramita glColor() e le sue varianti). Vediamo ora come effettuare il tratteggio.

La prima cosa da fare è abilitare lo stato GL_LINE_STIPPLE nella macchina tramite il comando enable. Dopodiché possiamo utilizzare il pratico comando:

glLineStipple(coefficente, pattern)

Questo comando imposta semplicemente lo stipple di una linea usando il tratteggio indicato in pattern. Ma come definiamo questo pattern? Tramite un numero esadecimale a 16bit. Qui bisogna fare un po’ di attenzione ma esiste un metodo infallibile per ricavare il numero corrispondente al pattern dei vostri sogni.

  1. Per prima cosa disegnate il pattern su un foglio quadrettato utilizzando solamente 16 quadretti.
  2. Sotto questo schema disegnate un 1 in corrispondenza dei quadratini anneriti e uno 0 in corrispondenza dei quadratini vuoti.  Ad esempio se voglio fare un pattern tratteggiato con 4pixel disegnati e 4 no avrò: 1111000011110000.
  3. Ribaltate il numero binario che avete trovato. Io ad esempio ottengo 0000111100001111.
  4. Raggruppate le cifre a gruppi di 4 e convertite il numero in esadecimale. Io ad esempio ottengo 0F0F.

Se vi domandate perché vada ribaltato vi basti sapere che dipende solamente dal modo con cui vengono letti i numeri nei calcolatori.
Il parametro coefficente è un semplice moltiplicatore. Se lo impostate ad 1 allora avrete identicamente il pattern che avete progettato, se lo impostate a 2 questo pattern verrò raddoppiato (ad esempio, nel mio caso, invece di 4pixel si e 4 no avrò 8pixel si e 8 no), con 3 triplicato e così via.

Ho questo esempio pronto:

from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *

def drawLine(x1,y1,x2,y2) :
    glBegin(GL_LINES)
    glVertex2f(x1,y1)
    glVertex2f(x2,y2)
    glEnd()

def init() :
    glClearColor(0,0,0,0)
    glShadeModel(GL_FLAT)

def display() :
    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(1,1,1)

    glEnable(GL_LINE_STIPPLE)

    #Prima Riga
    glLineStipple(1, 0x0101)
    drawLine(50, 125, 150, 125)
    glLineStipple(1, 0x00FF)
    drawLine(150, 125, 250, 125)
    glLineStipple(1, 0x1C47)
    drawLine(250, 125, 350, 125)

    #Seconda Riga
    glLineWidth(5)
    glLineStipple(1, 0x0101)
    drawLine(50, 100, 150, 100)
    glLineStipple(1, 0x00FF)
    drawLine(150, 100, 250, 100)
    glLineStipple(1, 0x1C47)
    drawLine(250, 100, 350, 100)
    glLineWidth(1)

    #Terza Riga
    glLineStipple(1, 0x1C47)
    glBegin(GL_LINE_STRIP)
    for i in range(7) :
        glVertex2f(50 + (i * 50), 75)
    glEnd()

    #Quarta Riga
    for i in range(6) :
        drawLine(50 + (i * 50), 50, 50 + (i+1) * 50, 50)

    #Quinta Riga
    glLineStipple(5, 0x1C47)
    drawLine(50, 25, 350, 25)

    glDisable(GL_LINE_STIPPLE)
    glFlush()

def reshape(w,h) :
    glViewport(0, 0, w, h)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluOrtho2D(0, w, 0, h)

if __name__=='__main__' :
    glutInit()
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
    glutInitWindowSize(400, 150)
    glutInitWindowPosition(100, 100)
    glutCreateWindow("Righe")
    init()
    glutDisplayFunc(display)
    glutReshapeFunc(reshape)
    glutMainLoop()

Uso tutti comandi già visti (tranne glLineWidth() ma credo che in questo caso il significato sia piuttosto intuitivo). La funzione drawLine() è una funzione personale che serve a snellire il codice. Potete usarla oppure usare direttamente ogni volta lo schema begin-vertex-end.

STIPPLE NEI POLIGONI

Ora invece vediamo come possiamo fare qualcosa di analogo con le campiture dei poligoni. Il meccanismo è analogo a quello delle linee. Per prima cosa si abilità lo stato GL_POLYGON_STIPPLE. Poi si imposta il pattern con:

glPolygonStipple(pattern)

Questa volta però disegnare il pattern è più complesso. Bisogna infatti utilizzare un array di 128 numeri esadecimali a 8bit al fine di formare una bitmap di 32×32 bit. Disegnare trame complesse è quindi molto più complesso del disegnare pattern per le righe. Proprio a causa di questa complessità e per il fatto che lo stipple di poligoni non è importante come lo stipple di linee, tralascerò per il momento il modo con cui vengono costruite queste bitmap. Ne farò forse un appendice.

Per il momento vi lascio questo codice di esempio:

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *

halftone = [ 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
        0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55 ]

def init() :
    glClearColor(0,0,0,0)
    glShadeModel(GL_FLAT)

def reshape(w,h) :
    glViewport(0, 0, w, h)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluOrtho2D(0, w, 0, h)

def display() :
    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(1,1,1)
    #Disegna un quadrato bianco
    glRectf(25, 25, 125, 125)
    #Disegna un quadrato stipple in halftone
    glEnable(GL_POLYGON_STIPPLE)
    glPolygonStipple(halftone)
    glRectf(125, 25, 225, 125)
    glDisable(GL_POLYGON_STIPPLE)
    glFlush()

if __name__ == '__main__' :
    glutInit()
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
    glutInitWindowSize(250, 150)
    glutCreateWindow("HalfTone")
    init()
    glutDisplayFunc(display)
    glutReshapeFunc(reshape)
    glutMainLoop()

Per questa volta è tutto. La prossima volta vedremo alcuni modi per evitare di inserire singolarmente tutti i vertici di una figura ed i rispettivi colori. Sostituendo quindi 20 istruzioni con una sola.

 

Nelle precedenti lezioni abbiamo visto come si riesca a disegnare qualunque poligono semplicemente inviando a OpenGL le coordinate dei suoi vertici. Per fare questo abbiamo anche visto che dobbiamo inserire il comando glVertex() all’interno di un blocco di istruzioni che comincia con:

glBegin(modes)

e termina con

glEnd()

Questa volta vediamo, anche grazie ad un piccolo programma di esempio come si comporta la renderizzazione al variare del parametro modes. Ogni modalità ha i suoi vantaggi e le sue modalità di utilizzo, ma andiamo a vedere queste modalità nel dettaglio:

  • GL_POINTS. Questa modalità interpreta i vertici come singoli punti.
  • GL_LINES. Questa modalità interpreta i vertici come segmenti. In pratica collega due vertici con una linea a coppie di due. Ad esempio dati 4 vertici v1, v2, v3 e v4 OpenGL traccerà una linea da v1 a v2 e da v3 a v4.
  • GL_LINE_STRIP. Questa modalità corrisponde ad una linea spezzata. In pratica OpenGL collegherà tutti i punti con una linea. v1 a v2, v2 a v3, v3 a v4 e così via.
  • GL_LINE_LOOP. Come sopra ma, in più, unisce anche l’ultimo vertice al primo.
  • GL_TRIANGLES. Questa modalità prende i vertici a gruppi di tre e forma dei triangoli.
  • GL_TRIANGLE_STRIP. Questa è forse la modalità più utilizzata. In pratica forma dei triangoli utilizzando come vertici l’ultimo vertice inserito più due vertici precedenti. Facciamo un esempio. Prendiamo 6 vertici. Inseriamo i primi tre formando il triangolo v1-v2-v3. Quando inseriremo il quarto vertice OpenGL creerà il triangolo v2-v3-v4. Poi v3-v4-v5 e così via fino all’ultimo triangolo. La modalità precedente, invece avrebbe disegnato solamente due triangoli: v1-v2-v3 e v4-v5-v6.
  • GL_TRIANGLE_FAN. Questa modalità è simile alla precedente ma ha una differenza fondamentale: il primo vertice inserito farà sempre parte dei triangoli. Così, supponendo di avere sempre i 6 vertici di prima, OpenGL disegnerà il triangolo v1-v2-v3, poi v1-v3-v4, poi v1-v4-v5 e così via.
  • GL_QUADS. Questa modalità è simile alla modalità GL_TRIANGLES. Ogni 4 vertici passati OpenGL disegna un quadrato.
  • GL_QUAD_STRIP. Questa modalità è analoga a GL_TRIANGLE_STRIP. Solamente con i quadrati al posto dei triangoli.
  • GL_POLYGON. Questa modalità interpreta ogni vertice come vertice di un poligono.

C’è però una cosa da tenere a mente: OpenGL non renderizza poligoni concavi. Gli unici poligoni che si possono renderizzare sono i poligoni convessi. Se volete disegnare poligoni non convessi dovete necessariamente suddividere il poligono da disegnare in un insieme di poligoni convessi (è sempre possibile). Solitamente vengono usati i triangoli poiché un triangolo è sempre convesso (questa proprietà non vale per i quadrilateri).

Un altra osservazione riguarda le facce. Un poligono bidimensionale in uno spazio tridimensionale ha due facce che chiameremo “davanti” e “dietro”. OpenGL decide quale faccia è “davanti” e quale è “dietro” semplicemente interpretando i vertici: viene considerata “davanti” la faccia dalla quale si vedono i vertici in senso anti-orario. E viceversa. L’ordine dei vertici è ovviamente l’ordine con i quali vengono inseriti: se inseriamo i vertici v1, v2 e v3 la faccia davanti sarà quella in cui posso vedere i vertici v1-v2-v3 ordinati in senso anti-orario.

Basta un piccolo sforzo di immaginazione, non è un concetto difficile. :)

Ora inseriamo un piccolo codice di esempio. In questo codice ad ogni click del mouse vengono disegnati sempre gli stessi punti (e sempre nello stesso ordine) ma con modalità diverse. Ad ogni click viene cambiata modalità. Una cosa istruttiva che potete fare è di disegnarvi i 9 punti che traccia il programma su un foglio di carta e di provare a prevedere cosa uscirà. Ovviamente alcune modalità con quei punti non si comportano molto bene (come GL_TRIANGLE_FAN e GL_POLYGON in quanto si verrebbero a formare poligoni concavi). Non ho però spostato i punti per far quadrare tutto, ho preferito lasciare i punti sempre fissi e far vedere come varia il risultato al variare delle modalità.

modes = [GL_POINTS, GL_LINES, GL_LINE_STRIP,
        GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP,
        GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP, GL_POLYGON]
       
index = 0

def init() :
    glClearColor(0,0,0,0)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    glOrtho(0,1,0,1,-1,1)
   
def mouse(button, state, x, y) :
    global index
    if button == GLUT_LEFT_BUTTON :
        if state == GLUT_DOWN :
            index += 1
    if index >= len(modes) :
        index -= len(modes)
    glutPostRedisplay()
   
def display() :
    glClear(GL_COLOR_BUFFER_BIT)
    glColor3f(1,1,1)
    glBegin(modes[index])
    glVertex2f(0.25, 0.25)
    glVertex2f(0.10, 0.50)
    glVertex2f(0.25, 0.85)
    glVertex2f(0.50, 0.50)
    glVertex2f(0.65, 0.85)
    glVertex2f(0.90, 0.85)
    glVertex2f(0.75, 0.50)
    glVertex2f(0.60, 0.15)
    glVertex2f(0.90, 0.15)
    glEnd()
    glFlush()
   
if __name__ == '__main__' :
    glutInit()
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
    glutInitWindowSize(250, 250)
    glutInitWindowPosition(100, 100)
    glutCreateWindow("Esempi Modalita'")
    init()
    glutDisplayFunc(display)
    glutMouseFunc(mouse)
    glutMainLoop()

Ho utilizzato solamente comandi già visti quindi non sto a rispiegarvi il codice. Il codice in questo caso non è nemmeno la cosa più importante. La cosa cruciale è guardare i vertici e vedere come vengono disegnati al variare della modalità.

Per questa volta è tutto. La prossima volta vedremo come modificare il disegno dei vertici ad esempio inserendo campiture nei poligoni o pattern nelle righe. Per fare, ad esempio, righe tratteggiate, spesse, fine, etc…

 

Prima di cominciare a spiegare il piccolo codice di esempio è importante fare qualche piccola premessa. La fluidità dell’animazione dipende dai frame per secondo (fps). I frame per secondo massimi dipendono dal refresh dello schermo. Gli schermi moderni vanno all’incirca a 60Hz, e quindi che in un secondo lo schermo proietta 60 immagini. Questo significa che ogni secondo possiamo rappresentare al massimo 60 immagini differenti e, quindi, possiamo avere al massimo 60fps.

Andare a 60fps, inoltre, significa che in un sessantesimo di secondo il nostro pc deve calcolare tutti i vertici dell’immagine successiva, inviarli alla scheda video e renderizzarli. Nel caso in cui l’elaborazione necessita più di un sessantesimo di secondo dobbiamo inserirci nell’intervallo successivo, ovvero due sessantesimi di secondo. In questo caso la nostra animazione crolla a 30fps (60/2). Se nemmeno due sessantesimi di secondo sono sufficienti allora passiamo a tre sessantesimi facendo abbassare i nostri fps a 20 (60/3) e così via.

Data la presenza di questi intervalli fissi il tempo di calcolo che “avanza” alla funzione Display viene occupato dalla funzione Idle. Questa è la funzione che viene eseguita quando nessun’altra funzione deve essere eseguita.

Il secondo punto è la presenza del doppio buffer. Usando un solo buffer infatti c’è il rischio che i vertici da visualizzare vengano modificati ancor prima di essere completamente renderizzati sullo schermo. Questo rischia di creare un fastidioso “effetto-fantasma” causato dalla coesistenza sullo schermo di due frame incompleti. Per ovviare a questo tutte le schede video moderne implementano il doppio buffer: secondo questo sistema un buffer viene utilizzato per la visualizzazione mentre il secondo viene utilizzato dal programma per immagazzinare i vertici del prossimo frame. Quando arriva il momento della visualizzazione questi due buffer vengono scambiati. In questo modo è garantito che vengano visualizzati solo frame completi.

from OpenGL.GLUT import *
from OpenGL.GL import *

spin = 0

def init():
    glClearColor(0,0,0,0)
    glShadeModel(GL_FLAT)

def display():
    glClear(GL_COLOR_BUFFER_BIT)
    glPushMatrix()
    glRotatef(spin, 0, 0, 1)
    glColor3f(1, 1, 1)
    glRectf(-25, -25, 25, 25)
    glPopMatrix()
    glutSwapBuffers()

def reshape(w, h):
    glViewport(0, 0, w, h)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    glOrtho(-50, 50, -50, 50, -1, 1)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

def spinDisplay():
    global spin
    spin += 0.5
    if spin > 360 :
        spin = spin - 360
    glutPostRedisplay()

def mouse(button, state, x, y) :
    if button == GLUT_LEFT_BUTTON :
        if state == GLUT_DOWN :
            glutIdleFunc(spinDisplay)
    elif button == GLUT_MIDDLE_BUTTON:
        if state == GLUT_DOWN :
            glutIdleFunc(None)

if __name__ == '__main__':
    glutInit()
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
    glutInitWindowSize(250, 250)
    glutInitWindowPosition(100, 100)
    glutCreateWindow("Quadrato Rotante")
    init()
    glutMouseFunc(mouse)
    glutDisplayFunc(display)
    glutReshapeFunc(reshape)
    glutMainLoop()

Questo programma fa due cose in più rispetto al precedente: fa ruotare il quadrato e permette di attivare/disattivare la rotazione con la pressione di due tasti del mouse (il centrale stoppa mentre il sinistro avvia).

Molte funzioni sono le stesse dell’ultima volta quindi non mi ripeterò. Vediamo le novità.

Innanzitutto potete notare che utilizzo GLUT_DOUBLE in DisplayMode. Questo serve ad attivare il doppio buffer.

Poi c’è la funzione glutMouseFunc() che serve ad impostare la funzione che servirà a gestire l’input del mouse. In questo caso ho usato una fantasiosa funzione mouse. Tale funzione prende quattro parametri:

  • button: indica quale bottone è stato premuto.
  • state: indica lo stato (premuto, alzato, click, etc…) vedremo più avanti queste cose in dettaglio.
  • x e y: sono, ovviamente, la posizione del puntatore.

Nella funzione mouse faccio una sola cosa: verifico quale tasto è stato premuto e a seconda del tasto modifico la funzione di Idle. Perché è la funzione IDLE che modifica i frame. La funzione display li mostra, la funzione IDLE li modifica (modificando i parametri utilizzati da display). Questo è il punto fondamentale.

Come potete vedere sposto la funzione di idle con il comando glutIdleFunc. La imposto a None (o NULL in C) per fermare il quadrato e alla funzione spinDisplay per ruotarlo. Quest’ultima funzione non fa altro che incrementare una variabile spin che rappresenta i gradi di rotazione.

La funzione display è del tutto simile a quella dell’esempio precedente. Viene aggiunta però la funzione glRotate la quale non fa altro che ruotare di spin gradi tutti i vertici che verranno inseriti fra glPushMatrix e glPopMatrix.

Un altra cosa accessoria è la funzione glutReshapeFunc che imposta la funzione da chiamare nel momento in cui la finestra viene ridimensionata. In questo caso la funzione evita solamente che vengano alterate le proporzioni.

EDIT: È disponibile la versione in C del codice di questa puntata. Lo trovate questo indirizzo

 

Stavo ripassando gran parte delle API delle OpenGL. Stavo facendo piccoli schemi e riassunti ed allora ho pensato: perché non condividere un po’ di questo lavoro? Così ci trascrivo qualche piccolo cenno di OpenGL senza nessuna intenzione di portare alla luce chissà quale serie di tutorial. Se poi esce qualcosa di appetibile meglio così.

Innanzitutto devo fare due appunti: il primo è che, per mia semplicità, userò il Python come linguaggio per il codice, tuttavia le API sono identiche sia in pyOpenGL che nell’OpenGL standard quindi anche se programmate in C potete facilmente convertire il tutto nel linguaggio a voi più familiare. Secondo, le libreria OpenGL prese da sole sono un po’ complicate, infatti di solito ci si appoggia alle librerie collegate GLUT. Queste librerie si incaricano di fare tutto il lavoro “esterno” alla visualizzazione grafica come gestione degli input e integrazione nel sistema a finestre. Usare GLUT è proprio l’approccio che userò anche io.

Iniziamo mostrando un piccolo programma che disegna un quadrato bianco su uno sfondo nero.

from OpenGL.GLUT import *
from OpenGL.GL import *

def init():
glClearColor(0,0,0,0)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0,1,0,1,-1,1)

def display():
glClear(GL_COLOR_BUFFER_BIT)
glColor3f(1,1,1)
glBegin(GL_POLYGON)
glVertex3f(0.25, 0.25, 0)
glVertex3f(0.75, 0.25, 0)
glVertex3f(0.75, 0.75, 0)
glVertex3f(0.25, 0.75, 0)
glEnd()
glFlush()

if __name__ == '__main__' :
glutInit()
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB)
glutInitWindowSize(250, 250)
glutInitWindowPosition(100, 100)
glutCreateWindow("Hello World")
init()
glutDisplayFunc(display)
glutMainLoop()

Partiamo quindi a spiegare il codice cominciando da l main. La prima funzione che incontriamo è glutInit(). Questa funzione si incarica di inizializzare GLUT. La versione canonica di questa funzione prende per argomento gli argomenti del main (argc e argv per chi conosce il C).

La seconda funzione è glutInitDisplayMode(). Questa funzione prende per parametri delle opzioni di visualizzazione. Nel nostro caso GLUT_SINGLE e GLUT_RGB i quali impostano la visualizzazione a singolo buffer (capiremo la differenza con il doppio buffer la prossima volta) e la visualizzazione RGB (ovvero con i colori tradizionalmente usati nei pc).

Gli altri tre comandi invece creano la finestra impostandone dimensione, posizione e titolo.

A questo punto entriamo in init(). In questa funzione vengono preimpostate alcune variabili di OpenGL. Non entrerò nel dettaglio ora, vi basti sapere che glClearColor() è la funzione per il colore di “sfondo” ovvero per quello usato durante la cancellazione. Questa funzione prende 4 parametri (rosso, verde, blu e trasparenza).

Un altra funzione importantissima è glutDisplayFunc() questa è la funzione che indica al motore grafico quale funzione chiamare nel momento in cui c’è bisogno di disegnare sullo schermo. In questo esempio passiamo semplicemente la funzione display la quale non fa altro che disegnare un quadrato bianco.

Come? Semplice. Per disegnare qualcosa bisogna passare al sistema le coordinate dei vertici secondo un algoritmo del tipo:

  • Si imposta il colore.
  • Si avverte il sistema che stiamo inserendo i vertici con glBegin().
  • Si passano le coordinate dei vertici con glVertex3f()
  • Si avverte il sistema che abbiamo terminato con glEnd()

Il parametro di glBegin è molto importante ma capiremo meglio il suo significato successivamente.

La funzione glFlush() è inutile per molte applicazioni. Ma voi mettetela. Così sarete sicuri che il vostro codice funzioni, ad esempio, in applicazioni di rete.

Alla fine, per chiudere in bellezza si lancia glutMainLoop() per avviare il programma.

Questo è solo un piccolo esempio. Disegnare un quadrato immobile non è ne bello ne utile. Ma questo esempio, nella sua semplicità, racchiude l’essenza di ogni programma OpenGL. La prossima volta vedremo come far ruotare questo dannato quadrato.

 


Python dispone di numerosissimi strumenti che facilitano e completano l’esperienza di programmazione. Il problema è, appunto, che sono troppi. Ogni volta bisogna ricercare i migliori fra la sterminata costellazione di strumenti di sviluppo.

E quindi ho deciso di fare una lisa. Soprattutto per me, dato che ogni volta che mi capita di reinstallare il sistema perdo un bel po’ di tempo a ricordarmi quali fossero tutti i tools che avevo installato.

IDE

La scelta dell’IDE è sempre la più importante. Sebbene Python non necessiti di chissà quale IDE avanzato, un po’ di comodità in più non fa certo male. La mia scelta ricade a pari merito su due IDE molto importanti:

Per il momento sto utilizzando la seconda configurazione ma in passato ho usato la prima. A voi la scelta.

CHECKER

Python è un linguaggio interpretato, ragion per cui gran parte degli errori di sintassi vengono fuori solamente in fase di esecuzione. Ci sono però programmi in grado di individuarli da subito.

Questa è la mia scelta. Fa tutte le cose di PyChecker e, in aggiunta, fa anche alcuni controlli riguardo le convenzioni stilistiche di Python. Un ottimo “tutor”. Inoltre è integrato con PyDev, quindi se usate Eclipse è veramente una mano santa.

DEBUGGER

Il debug in python è integrato. Basta importare il modulo pdb e lanciare il programma con:

<span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace; line-height: 18px; font-size: 12px; white-space: pre;">pdb.run('mymodule.test()')

Vi rimando alla guida ufficiale per maggiori informazioni.

TESTING FRAMEWORK

Gli ambienti di test sono strumenti che permettono di scrivere dei test per verificare il buon funzionamento di una classe o di un modulo che abbiamo appena scritto. In Java io utilizzavo JUnit. È quindi naturale che in Python abbia scelto:

PyUnit ricalca molto da vicino la sintassi di JUnit. Inoltre è integrato con Eclipse. Per test semplici è veramente intuitivo, per roba più complessa basta dare una letta alla documentazione.

CODE COVERAGE

Gli strumenti di code coverage sono tools che tengono traccia di quale parte di codice eseguiamo durante un test e di quale non eseguiamo. È molto facile infatti che durante un test capiti di trovarsi ad eseguire, ad esempio, solamente un ramo di un IF e che, come al solito, l’errore si nasconda nel ramo che non eseguiamo mai. I programmi di code coverage ci segnalano proprio questa eventualità.

Questo è il programma di coverage in Python per eccellenza. Integrata con PyDev. Esiste anche un espansione (figleaf) che però non ho mai avuto modo di provare.

DOCUMENTAZIONE

Scrivere la documentazione è essenziale. Generare la documentazione dai commenti del codice sorgente è ancora più essenziale in quanto ci risparmia una marea di tempo. In Java esiste il famoso javadoc. Ovviamente il Python non è da meno.

Epydoc, di cui ho scritto anche una rapida guida tempo fa, è un tool fenomenale. Non è perfettamente integrato con Python 3 (almeno nell’ultima versione che ho usato) ma fa dannatamente bene il suo lavoro.

Credo che per il momento la lista sia piuttosto esaustiva. Spero vi sia utile.

Alla prossima. :)

© 2008-2012 SlashCode Suffusion theme by Sayontan Sinha