Tesi di laurea triennale in cui si discute l\'implementazione di una libreria orientata agli oggetti in Python per il calcolo di curve e superfici parametriche, in particolare NURBS, che trova applicazioni nell\'ambito della grafica 2D e 3D.
Sviluppo di una libreria orientata agli oggetti per il calcolo di NURBS con applicazioni alla grafica 2D e 3D
1. Universit`a degli Studi di Salerno
Facolt`a di Scienze Matematiche Fisiche e Naturali
Tesi di Laurea di I livello in
Informatica
Sviluppo di una libreria orientata
agli oggetti per il calcolo di NURBS
con applicazioni alla grafica 2D e
3D
Relatore Candidato
Dott. Mario Annunziato Antonio Sanfelice
Matricola 05102/00997
Anno Accademico 2009 - 2010
3. Ringraziamenti
Ai miei genitori, che mi hanno dato fiducia e mi hanno concesso la possibilit`a
di raggiungere questo obiettivo.
A Mariapia, che ha condiviso le mie gioie e i miei dolori.
A Demia, Pasquale, Stefania, Enzo, Gianni e a tutti coloro che hanno reso
questi quattro anni i pi`u divertenti e spensierati della mia vita nonostante
il carico di lavoro che ho dovuto affrontare.
A Tonino, i cui cornetti e caff`e, mi hanno tenuto in piedi in questi anni.
A tutti coloro che si chiedono come mai, in una facolt`a il cui nome `e
“Scienze Matematiche, Fisiche e Naturali”, vengono torturati con sommato-
rie, derivate ed integrali, e che non importa come glielo si spieghi, continuano
a porsi bizzarri interrogativi.
Ai Dream Theater, agli Iron Maiden, ai Within Temptation, ai Night-
wish, agli AC/DC, ai Gotthard, ai Guns ’n Roses, ai Three Doors Down,
ai Black Sabbath, ai Metallica, ai Testament, ai Children Of Bodom, agli
Edguy, ai Blind Guardian, ai Sonata Artica, ai Dire Straits, ai Queen, agli
X-Japan, a Joe Satriani, ad Eric Clapton, ad Eric Johnson, agli Alter Brid-
ge, agli Europe, ai Firewind, a Mozart, a Beethoven, a Bach, che da una
vita mi danno l’adrenalina necessaria ad affrontare i momenti belli e meno
belli.
Alla birra che, ne dicano quel che vogliono, mi ha aiutato a risolvere
alcuni dei peggiori rompicapo che ho incontrato durante lo studio.
A Leonardo Da Vinci, da cui ho imparato che al mondo ci sono troppe
cose interessanti per dedicare la vita ad una sola di esse.
A tutti voi,
Grazie
iii
5. v
Sommario
Le NURBS (Non Uniform Rational B-Splines) sono lo standard attuale per
quanto riguarda la grafica 2D e 3D e rappresentano il modo pi`u generale di
rappresentare una curva o una superficie. Sebbene esistano diverse librerie e
software gratuiti che consentono di calcolare questo tipo di curve e superfici,
alcune di esse sono state abbandonate (come ad esempio la libreria nurbs++
[13]), sono poco documentate o eccessivamente complesse ( come la libreria
openNURBS [5]), altre invece sono progettate per il calcolo numerico (esem-
pio modulo NURBS per octave [15]). L’obiettivo di questa tesi `e lo sviluppo
di una libreria orientata agli oggetti portabile, semplice da usare e che possa
essere utilizzata in diversi ambiti. La suddetta libreria `e stata sviluppata in
Python, in quanto `e presente per tutte i principali sistemi operativi, `e sup-
portato da un gran numero di librerie e consente di risolvere con poche righe
di codice problemi complessi. Nella tesi vengono riassunte le conoscenze ne-
cessarie per lavorare con curve e superfici parametriche, in particolare con le
NURBS e viene illustrata la libreria da me sviluppata che oltre a permettere
di calcolare diversi tipi di curve e superfici utilizando una rappresentazione
polinomiale a tratti (curve di B´ezier, B-Spline, NURBS), offre strumenti per
risolvere il problema dell’approssimazione di un insieme di punti, sia in due
che in tre dimensioni, utilizzando le B-Spline. Quest’ultima caratteristica
si rivela di particolare interesse e trova applicazioni anche al di fuori della
grafica, permettendo ad esempio di calcolare traiettorie per robot mobili, o
di approssimare (o ricostruire) segnali audio precedentemente campionati,
inoltre versioni pi`u complesse di B-Spline e NURBS trovano applicazione
in analisi nuemerica e in altri campi di ricerca avanzati come strumenti di
approssimazione di funzioni.
11. Capitolo 1
Introduzione
Scienza `e tutto ci`o che comprendiamo abbastaza
bene da spiegarlo ad un computer, tutto il resto `e
arte.
Donald E. Knuth
Le NURBS (Non Uniform Rational B-Spline) rappresentano lo standard
attuale per la rappresentazione di curve e superfici in computer grafica. La
loro capacit`a di rappresentare con accuratezza anche le forme pi`u complesse,
le rende uno strumento particolarmente utile qualora si abbia la necessit`a
di disegnare modelli 2D e 3D, come nelle applicazioni di prototipazione ra-
pida: applicazioni volte alla creazione automatica di prototipi solitamente
utilizzate in ambito industriale. Il loro sviluppo inizi`o negli anni ’50 con lo
scopo di creare un modello matematico in grado di descrivere con precisione
superfici a forma libera, da poter utilizzare ad esempio per modellare chiglie
di navi, ali di aerei, carrozzerie di automobili. Pionieri nello sviluppo di
queste tecniche di approssimazione di curve e superfici furono Pierre B´ezier
e Paul de Casteljau, i quali lavorarono praticamente in parallelo entrambi
all’oscuro del lavoro dell’altro [25]. Sebbene siano nate come strumento di
modellazione CAD e rappresentino tutt’oggi uno degli strumenti fondamen-
tali nella grafica 2D e 3D, le B-Spline e le NURBS trovano applicazione
anche in campi diversi dalla grafica. Esse infatti trovano applicazione in
analisi numerica e in altri ambiti di ricerca avanzata come strumento per
approssimare funzioni. Esistono diverse librerie gratuite che permettono il
calcolo di curve e superfici NURBS, tutta via alcune di esse presentano alcu-
ni problemi: alcune librerie non vengono aggiornate da anni, altre sono poco
(o per niente) documentate, rendendo difficile qualsiasi tipo di intervento su
di esse o semplicemente mancano le funzionalit`a di cui si aveva bisogno. In
questo lavoro ci proponiamo di sviluppare una libreria orientata agli oggetti
per il calcolo di NURBS e di altri tipi di curve e superfici parametriche,
1
12. 2 CAPITOLO 1. INTRODUZIONE
Figura 1.1: La creazione di una superfice NURBS all’interno del software di
modellazione 3D Blender [1]
che consenta inoltre di risolvere il problema dell’approssimazione di punti,
sia sul piano che nello spazio. Quest’ultima caratteristica `e di particolare
interesse. Sebbene siano nate come strumento per la grafica, le B-Spline, le
NURBS e versioni pi`u complesse di entrambe trovano diverse applicazioni
anche in ambiti molto diversi, ad esempio in analisi numerica ed altri campi
di ricerca avanzati. Basti pensare che esse vengono utilizzate per:
ˆ calcolare la traiettoria da far seguire ad un robot per fargli raggiungere
una data destinazione [21, 22];
ˆ approssimare (o nel caso migliore ricostruire) un segnale audio o video
campionato in precedenza [23];
ˆ controllare le funzioni di densit`a di probabilit`a output di sistemi lineari
stocastici [26].
Nella tesi verranno illustrati diversi approcci per il calcolo delle principali
curve e superfici parametriche, mettendo a confronto tecniche di calcolo
naive con tecniche pi`u complesse. Saranno infine proposti alcuni esempi
di utilizzo della libreria sviluppata in campi d’applicazione molto diversi
fra loro, per sottolineare l’ampio bacino di applicazioni che le NURBS che
possono trovare.
Il paradigma
Le curve e le superfici trattate, oltre ad essere in numero discreto, presentano
attributi e schemi comuni: per questi motivi abbiamo optato per il paradig-
ma di programmazione orientata agli oggetti. Questo paradigma, rispetto a
quello procedurale, consente di racchiudere in un’unica entit`a (denominata
13. 3
classe) sia i dati che i metodi che lavorano su di essi e di definire relazioni fra
le entit`a stesse. Utilizzando questo paradigma e modellando quindi classi
rappresentanti i vari tipi di curve e superfici si ottiene un codice dall’elevata
modularit`a, il che facilita eventuali interventi di manutenzione, inoltre `e pi`u
semplice per chi conosce il contesto districarsi all’interno del codice stesso.
Il linguaggio
Per l’implementazione abbiamo scelto il Python: linguaggio versatile che
permette di sviluppare seguendo i paradigmi ad oggetti, procedurale e fun-
zionale. Questo linguaggio ha guadagnato negli anni un gran numero di
consensi, in quanto offre un ottimo compromesso fra velocit`a e semplicit`a.
Il Python `e utilizzato negli ambiti pi`u disparati, dallo sviluppo web allo
sviluppo di videogiochi, dallo sviluppo di software di computer grafica al
calcolo scientifico. Si pensi che Google e la NASA fanno un forte uso di Py-
thon nei loro rispettivi ambiti (per ulteriori dettagli vedere le sezioni “suc-
cess stories” e “quotes”su [6]). Essendo Python un linguaggio interpretato,
i software scritti in questo linguaggio possono essere eseguiti su qualsiasi
piattaforma abbia un interprete installato, facendo s`ı che chi sviluppa non
debba preoccuparsi del discorso portabilit`a.
Sommario dei capitoli
Per facilitare la consultazione della tesi, vengono di seguito riassunti i con-
tenuti dei singoli capitoli.
Il Capitolo 2 copre l’aspetto matematico del lavoro svolto, introducendo
le notazioni e le definizioni utilizzate per sviluppare la libreria oggetto
di questa tesi.
Il Capitolo 3 riassume i concetti base del linguaggio Python, illustrando
alcuni particolari della sintassi in modo da rendere pi`u semplice la
comprensione delle implementazioni.
Il Capitolo 4 illustra una ad una le classi che compongono la libreria,
mostrando gli algoritmi utilizzati e la loro implementazione.
Il Capitolo 5 mostra alcuni esempi di utilizzo e fornisce alcuni misurazioni
del tempo di esecuzione. Inoltre vengono illustrati alcuni esempi di
applicazione della libreria.
In Appendice A `e presente la documentazione della libreria, corredata di
diagrammi UML dei casi d’uso e delle classi.
In Appendice B `e possibile consultare il codice sorgente della libreria
nella sua interezza.
15. Capitolo 2
Spline, B-Spline e NURBS
Se le persone non credono che la matematica sia
semplice, `e solo perch´e non hanno ancora
realizzato quanto `e complicata la vita.
John von Neumann
In questo capitolo vengono presentate i vari tipi di curve e superfici
oggetto di questo lavoro, partendo da alcune definizioni di base fino ad ar-
rivare al metodo pi`u generale per la rappresentazione di curve e superfici:
le NURBS. Oltre a [12, 19], materiale di riferimento per questo capitolo, `e
possibile visitare [11] per un tutorial interattivo su curve e superfici parame-
triche, mentre il materiale pubblicato dal progetto MIT OpenCourseWare
([16]) contiene maggiori dettagli, dal punto di vista analitico, sulle propriet`a
delle curve e delle superfici qui presentate.
2.1 Le basi
In questo paragrafo verranno illustrati i concetti di base riguardanti la pa-
rametrizzazione delle curve, la loro rappresentazione a tratti, i tipi di conti-
nuit`a e altri argomenti necessari a comprendere successivamente il compor-
tamento di curve di B´ezier, B-Spline e Nurbs, fino ad arrivare alle superfici.
2.1.1 Rappresentazione di curve
Una curva `e una successione continua di punti. Esistono tre modi principali
per rappresentare una curva. La prima, pi`u nota ed utilizzata nei primi
corsi di matematica, `e la rappresentazione esplicita. La rappresentazione in
forma esplicita `e una rappresentazione del tipo:
y = f(x)
5
16. 6 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
Figura 2.1: Una spirale `e una curva polidroma
Questo tipo di rappresentazione, che mette in evidenza la dipendenza della
variabile y dalla variabile x ha le seguenti caratteristiche:
ˆ Permette di disegnare facilmente la curva;
ˆ Permette di verificare facilmente se un punto appartiene la curva;
ˆ Non permette di rappresentare facilmente curve polidrome1;
ˆ `E dipendente dagli assi, `e quindi difficile trasportare la curva in un
altro sistema di riferimento.
Proprio quest’ultima caratteristica `e uno svantaggio pesante. Infatti con
questo tipo di rappresentazione `e possibile rappresentare solo curve che han-
no associato ad ogni valore della variabile x un unico valore della variabile
y. `E quindi difficile rappresentare curve come quella mostrata in figura 2.1
Per ovviare a questo problema si pu`o utilizzare la rappresentazione in
forma implicita:
f(x, y) = 0
Questo tipo di rappresentazione
ˆ Permette di rappresentare curve polidrome;
ˆ Permette di verificare facilmente se un punto appartiene o meno alla
curva;
ˆ Rende difficile disegnare la curva;
ˆ `E dipendente dagli assi, quindi `e difficile trasportare la curva in un
altro sistema di riferimento.
1
Una funzione polidroma `e una funzione f : X → P(Y) (dove P(Y) rappresenta l’insie-
me delle parti di Y) che ad un dato valore della variabile x associa uno o pi`u valori della
variabile y
17. 2.1. LE BASI 7
La terza rappresentazione, ovvero quella in forma parametrica, `e la
pi`u flessibile. L’idea consiste nell’esprimere sia la variabile x che la variabile
y come funzioni di un parametro esterno, che chiameremo t. Otteniamo
quindi una rappresentazione del tipo:
x = f1(t)
y = f2(t)
Il motivo per cui la rappresentazione in forma parametrica `e la pi`u flessibile,
diventa ovvio con un piccolo esempio. Supponiamo di voler disegnare una
circonferenza di raggio r con centro nell’origine:
y = ± r2 − x2
L’equazione in forma esplicita costringe a disegnare due semicirconferenze,
la prima y = +
√
r2 − x2, e la seconda y = −
√
r2 − x2. Passando alla forma
implicita, diventa comodo generalizzare e considerare una circonferenza di
centro (x0, y0):
(y − y0)2
+ (x − x0)2
= r2
y2
+ y2
0 − 2yy0 + x2
+ x2
0 − 2xx0 = r2
y2
+ y2
0 − 2yy0 + x2
+ x2
0 − 2xx0 − r2
= 0
Salta all’occhio che disegnare la circonferenza utilizzando la notazione im-
plicita `e tutt’altro che semplice. L’equazione in forma parametrica per una
circonferenza di raggio r e centro (x0, y0) invece `e la seguente:
C =
x(u) = x0 + r · cos(t) t ∈ [0, 2π]
y(u) = y0 + r · sin(t)
La forma parametrica `e visibilmente pi`u semplice `e consente di calcolare di-
rettamente un qualsiasi punto della circonferenza, cosa che non era possibile
n´e con la notazione esplicita n´e con quella implicita.
Propio per l’estrema flessibilit`a e per la possibilit`a di poter definire for-
me molto particolari in modo relativamente semplice, la rappresentazione
parametrica `e molto utilizzata in computer grafica.
2.1.2 Tipi di parametrizzazione
Rappresentare una curva nel piano in forma parametrica significa creare
una applicazione f : R → R2 che ad ogni valore del parametro associa una
coordinata (x, y) sul piano. Una delle comodit`a nell’usare questo tipo di
rappresentazione `e che il parametro pu`o rappresentare qualsiasi cosa: pu`o
essere il tempo, un angolo, una frequenza, qualsiasi cosa si ritiene debba
influire sulla forma della curva. Di solito si utilizza un parametro che spazi
18. 8 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
all’interno dell’intervallo [0, 1], in modo che f(0) identifichi l’inizio della
curva e f(1) ne identifichi la fine, questo tipo di parametro viene usualmente
identificato con u. L’affermazione precedente potrebbe portare erronamente
a pensare che f(0.5) identifichi la met`a della curva, ma non `e cos`ı. Il fatto
che u = 0.5 identifichi o meno la met`a della curva dipende dal tipo di
parametrizzazione che stiamo usando:
Naturale: In questo tipo di parametrizzazione, u non `e legato alla curva
in se, ma allo spazio all’interno del quale la curva `e definita. Quindi
se u = 0.5, f(u) indica il punto della curva al centro dello spazio di
definizione.
Lunghezza d’arco: In questo tipo di parametrizzazione u segue l’anda-
mento della curva, quindi se u = 0.5, f(u) identifica il punto centrale
della curva.
Avendo una parametrizzazione naturale con parametro u, per conoscere
il parametro s che rappresenta la lunghezza dell’arco dal punto f(0) al punto
f(u).
s =
u
0
|f (u)|2
du (2.1)
Per poter utilizzare la rappresentazione a lunghezza d’arco, bisogna es-
sere in grado di risolvere l’equazione (2.1) rispetto ad u dato s, il ch´e non
`e sempre possibile o comunque semplice.
2.1.3 Rappresentazione a tratti
Spesso pu`o risultare difficile trovare una funzione parametrica capace di
descrivere una curva nella sua interezza. In questi casi pu`o risultare utile
un approccio tipo divide et impera, ovvero dividere la curva in tratti la cui
descrizione tramite funzione parametrica sia pi`u semplice. Se ad esempio
volessimo dividere un ipotetica curva in due tratti, la sua descrizione sarebbe
qualcosa di simile a:
f(u) =
f1(2 · u) se u ≤ 0.5
f2(2 · u − 1) se u > 0.5
Dove f1 `e la funzione parametrica che rappresenta il primo tratto e f2 `e
quella che rappresenta il secondo, entrambe sono definite per u ∈ [0, 1]. Nel
definire le funzioni f1 e f2 bisogna assicurarsi che i due tratti combacino. Se
f1(1) = f2(0) i due tratti non formano un unica curva continua.
Un altro problema che riguarda la rappresentazione a tratti riguarda
il tipo ed il numero di tratti da usare. Sicuramente, per una questione
di semplicit`a, conviene utilizzare tratti dello stesso tipo (ad esempio solo
polinomi cubici). La scelta del numero di tratti da usare permette di decidere
con che approssimazione ricreare la curva. Col tendere del numero di tratti
19. 2.1. LE BASI 9
all’infinito, la curva pu`o essere riprodotta esattamente. Un vantaggio sicuro
della rappresentazione a tratti `e che permette di decidere, a seconda delle
applicazioni, se si vuole una rappresentazione pi`u semplice o pi`u precisa. Si
possono utilizzare pochi tratti pi`u complessi, oppure molti tratti semplici
( ad esempio polinomi di primo grado) ed ottenere comunque una buona
approssimazione della curva originale.
2.1.4 Propriet`a delle curve
Nel descrivere una curva, abbiamo bisogno di conoscere alcune sue caratte-
ristiche. Per le curve pi`u comuni, queste caratteristiche sono specifiche per
il tipo di curva. Ad esempio, per un cerchio basta specificare il raggio e la
posizione del centro. Per curve a forma libera per`o, c’`e bisogno di conoscere
diverse caratteristiche. Alcune caratteristiche sono generali, ad esempio se
la curva `e chiusa o meno, se passa per un dato punto o se assume una certa
direzione almeno una volta. Propriet`a pi`u interessanti per`o, specialmente se
la curva viene rappresentata a tratti, sono le propriet`a locali.
Le propriet`a locali includono:
ˆ Continuit`a;
ˆ Posizione di un determinato punto della curva;
ˆ Direzione ad un determinato punto della curva (derivata prima);
ˆ Curvatura (derivata seconda) e altre derivate;
2.1.5 Continuit`a
Il discorso inerente la continuit`a assume una certa importanza quando si
tenta di rappresentare una curva a tratti. Come detto nel paragrafo 2.1.3,
siano fk e fk+1 le funzioni parametriche descriventi il tratto k-esimo e k +
1-esimo della curva, se fk(1) = fk+1(0) la curva risulta spezzata.
Oltre alla posizione, si potrebbe verificare che anche le derivate prime
dei due tratti nei punti in cui esse vanno a congiungersi siano uguali. Questo
perch´e se fk (1) = fk+1 (0), la curva potrebbe presentare un cambio di di-
rezione troppo repentino, che visivamente potrebbe tradursi, ad esempio, in
uno spigolo. In generale, si dice che una curva ha una continuit`a di tipo Cn
se tutte le derivate fino all’n-esima combaciano fra un tratto e l’altro della
curva. Fino a che derivata imporre la continuit`a `e una scelta che dipende
dalle applicazioni: se la curva descrive una traiettoria, una discontinuit`a
sulla derivata 2° significherebbe un brusco cambio di accelerazione, quindi
in questo caso potrebbe essere utile una continuit`a di tipo C3, mentre se la
curva rappresenta il profilo di un oggetto che deve attraversare un fluido (lo
scafo di una barca, o l’ala di un aereo), allora una discontinuit`a nella 4° o
5° derivata potrebbe causare problemi (vedere [12]).
20. 10 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
Figura 2.2: Differenze fra diverse condizioni di continuit`a [12]
Questo tipo di continuit`a `e per`o un p`o restrittiva, e non permette di
modellare alcuni tipi di curve. Per questo si introduce una continuit`a pi`u
“debole”, denominata continuit`a geometrica. Con questo tipo di conti-
nuit`a si impone che la derivata nel punto finale di un tratto abbia solo la
direzione in comune con la derivata del punto iniziale del tratto successivo,
mentre il modulo pu`o essere diverso. Ci`o significa che laddove una continuit`a
di tipo C1 richiede che
fk (1) = fk+1 (0)
una continuit`a di tipo G1 richiede che
fk (1) = c · fk+1 (0)
per qualche valore c. Ovviamente, una curva che `e Cn continua `e necessa-
riamente anche Gn continua, ma non viceversa.
2.1.6 Rappresentazione Matriciale di Polinomi
Supponiamo di voler scrivere l’equazione parametrica di una linea che collega
due punti, p0 e p1. Si pu`o utilizzare la formula per la combinazione convessa
dei due punti:
f(u) = (1 − u)p0 + up1
Tuttavia un modo pi`u semplice per rappresentare la linea pu`o essere quello
di scrivere il polinomio di primo grado in u:
f(u) = a0 + ua1
Specificare la linea tramite i suoi punti estremi `e sicuramente pi`u semplice,
ma la rappresentazione tramite i coefficienti a0 e a1 ha il vantaggio di essere
21. 2.1. LE BASI 11
pi`u generale. Infatti, tramite il polinomio in u `e possibile rappresentare una
generica curva di grado n-esimo nel seguente modo:
f(u) =
n
i=0
ui
ai
Questa forma `e detta forma canonica. Il precedente polinomio pu`o esse-
re facilmente rappresentato in forma vettoriale definendo un vettore delle
potenze di u:
u = 1 u u2 u3 . . . un T
ottenendo quindi:
f(u) = uT
a
Sebbene la forma canonica abbia i suoi vantaggi, pu`o non essere la scelta
pi`u comoda per rappresentare una curva. Per la linea ad esempio, la strada
pi`u comoda rimane quella di specificare i punti estremi, e quindi imporre
che p0 sia il punto dove la linea si trova quando u = 0 e p1 sia il punto
dove la linea si trova quando u = 1. Per passare da una rappresentazione
all’altra possiamo scrivere:
p0 = f(0) = [1 0] [a0 a1]T
p1 = f(1) = [1 1] [a0 a1]T
Risolvendo rispetto ad a0 ed a1 otteniamo:
a0 = p0
a1 = p1 − p0
Il tutto pu`o essere reso pi`u compatto racchiudendo in singoli vettori i punti
ed i coefficienti del polinomio:
p0
p1
=
1 0
1 1
a0
a1
oppure, se definiamo con p il vettore dei punti, con C la matrice con i valori
di u e con a il vettore dei coefficienti:
p = C a (2.2)
p `e denominato vettore dei punti di controllo2, ed ogni suo elemento
`e definito punto di controllo. C `e la matrice costante. Si pu`o risolvere
l’equazione 2.2 rispetto ad a trovando l’inversa di C. Questa matrice, che
2
Si noti che dato che un punto pi `e un vettore del tipo [pix piy], p `e in realt`a una matrice
2xn, dove n `e il numero di punti di controllo specificati. Di conseguenza, anche il relativo
coefficiente ai `e un vettore, il che `e ovvio se si ricorda che con la notazione parametrica
si scrive un polinomio per ogni variabile posizionale, quindi ci sar`a un coefficiente aix per
la variabile x ed un coefficiente aiy per la variabile y
22. 12 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
denoteremo con B, `e denominata matrice di base. La matrice fa da ponte
tra i punti di controllo e i coefficienti della forma canonica, fornendo quindi
un modo pi`u comodo per rappresentare una curva:
f(u) = u B p
Supponiamo ora di voler parametrizzare una curva di 2° grado, specifi-
cando il punto iniziale, il punto centrale e il punto finale. In questo caso
scriviamo:
p0 = f(0) = a0 + 01 a1 + 02 a2
p1 = f(0.5) = a0 + 0.51 a1 + 0.52 a2
p2 = f(1) = a0 + 11 a1 + 12 a2
La matrice costante `e quindi:
C =
1 0 0
1 .5 .25
1 1 1
da cui otteniamo la matrice di base:
B = C−1
=
1 0 0
−3 4 −1
2 −4 2
Imporre solo condizioni posizionali per`o non basta. Per avere pi`u con-
trollo sull’andamento della curva `e utile introdurre delle condizioni sulle
derivate.
Imporre tali condizioni `e semplice, basta derivare la forma canonica
rispetto ad u, quindi per una curva di secondo grado:
f(u) = a0 + a1 u + a2 u2
La derivata prima `e pari a:
f (u) =
df
du
= a1 + 2 a2 u
Mentre la derivata seconda `e:
f (u) =
d2f
du
= 2 a2
Volendo parametrizzare quindi una curva di secondo grado, specificando
posizione, derivata prima e derivata seconda del suo punto centrale (u = 0.5):
p0 = f(0.5) = a0 + 0.51 a1 + 0.52 a2
p1 = f (0.5) = a1 + 2 (0.5) a2
p2 = f (0.5) = 2 a2
23. 2.1. LE BASI 13
La matrice costante che se ne ricava `e:
C =
1 .5 .25
1 1 1
0 0 2
e la matrice di base `e:
B = C−1
=
1 −.5 .125
0 1 −.5
0 0 .5
2.1.7 Funzioni di base
Se la matrice di base B `e nota, la si pu`o moltiplicare per il vettore delle
potenze del parametro u ottenendo un vettore di funzioni:
b(u) = u B
Le funzioni che compongono questo vettore sono denominate funzioni di
base. Grazie a queste funzioni, `e possibile rappresentare una curva come
combinazione lineare dei suoi punti di controllo:
f(u) =
n
i=0
bi(u) pi
Una volta nota la matrice di base quindi, ci sono due strade per calcolare la
curva:
ˆ moltiplicare la matrice di base per i punti di controllo, ottenendo i
coefficienti della forma canonica.
ˆ moltiplicare la matrice di base per il vettore delle potenze del parame-
tro u, ottenendo le funzioni di base.
2.1.8 Spline cubiche di uso comune
In questa sezione esamineremo alcune spline di uso comune, utili anche per
capire in quanti modi diversi si pu`o parametrizzare una curva. Le spline
cubiche (che utilizzano quindi polinomi di 3° grado) sono molto utilizzate
nell’ambito della grafica vettoriale. Questo perch´e i polinomi di terzo grado
offrono un ottimo compromesso fra versatilit`a e semplicit`a. Nell’interpolare
un insieme di punti un polinomio di terzo grado disegna la curva con la
minima curvatura, il che vuol dire che la curva ottenuta “oscilla” di meno
rispetto a quella ottenuta tramite un polinomio di grado pi`u alto.
24. 14 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
Spline cubica naturale
La spline cubica naturale si ottiene specificando posizione, derivata prima,
derivata seconda per il punto iniziale di ogni tratto. Questo tipo di spline ha
una continuit`a di tipo C2. Per un singolo tratto bisogna specificare anche
la posizione del punto finale. Imponendo questi vincoli otteniamo:
p0 = f(0) = a0 + 01 a1 + 02 a2 + 03 a3
p1 = f (0) = a1 + 2 01 a2 + 3 02 a3
p2 = f (0) = 2 a2 + 6 01 a3
p3 = f(1) = a0 + 11 a1 + 12 a2 + 13 a3
Quindi la matrice costante `e:
C =
1 0 0 0
0 1 0 0
0 0 2 0
1 1 1 1
e la matrice di base `e:
B = C−1
=
1 0 0 0
0 1 0 0
0 0 .5 0
−1 −1 −.5 1
La figura 2.1.8 mostra il grafico delle funzioni di base per questa spline. Lo
svantaggio principale della spline cubica naturale, `e che c’`e poco controllo
sulla parte finale della curva. Mentre per il punto iniziale viene specificata la
posizione, la derivata prima e la derivata seconda, per il punto finale viene
specificata solo la posizione, il che rende difficile modificare l’andamento
della curva a proprio piacimento.
Spline di Hermite
Le spline cubiche di Hermite si ottengono specificando posizione e derivata
prima per entrambi i punti estremi. Diversi tratti di una spline di Hermi-
te possono essere concatenati formando una curva con continuit`a C1. Un
vantaggio delle spline di Hermite consiste nel fatto che modificando uno
dei punti di controllo, solo l’intorno di quel punto viene modificato e non
l’intera curva. Questa propriet`a `e nota come controllo locale. Inoltre, le
spline di Hermite interpolano tutti i punti di controllo relativi alla posizione.
Imponendo quindi le condizioni dette precedentemente otteniamo:
p0 = f(0) = a0 + 01 a1 + 02 a2 + 03 a3
p1 = f (0) = a1 + 2 01 a2 + 3 02 a3
p2 = f(1) = a0 + 11 a1 + 12 a2 + 13 a3
p3 = f (1) = a1 + 2 11 a2 + 3 12 a3
25. 2.1. LE BASI 15
Figura 2.3: Funzioni di base della spline cubica naturale
La matrice costante `e:
C =
1 0 0 0
0 1 0 0
1 1 1 1
0 1 2 3
e la matrice di base risulta:
B = C−1
=
1 0 0 0
0 1 0 0
−3 −2 3 −1
2 1 −2 1
La figura 2.4 mostra le funzioni di base per la spline cubica di Hermite.
Spline cardinale
Una spline cardinale cubica `e una spline interpolante con continuit`a di tipo
C1. Questo tipo di spline intepola tutti i suoi punti di controllo ad ecce-
zione del primo e dell’ultimo. Ci`o che contraddistingue la spline cardinale
`e l’utilizzo di un parametro di tensione3 t. Questo parametro indica quanto
3
Si pensi alla tensione nel senso meccanico del termine, ad esempio quella di una corda
soggetta a trazione
26. 16 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
Figura 2.4: Funzioni di base della spline cubica di Hermite
la curva deve essere tesa fra un punto di controllo e l’altro, effettuando una
scalatura delle derivate nei punti di controllo. La derivata di un punto di
controllo pi viene calcolata a partire dalla posizione dei punti di controllo
adiacenti pi−1 e pi+1: la direzione `e parallela alla retta che connette pi−1 e
pi+1, mentre il modulo `e pari alla loro distanza. Il parametro di tensione t
agisce scalando la derivata di un fattore 1
2(1 − t).
I vincoli per una spline cardinale quindi sono:
f(0) = p1
f(1) = p2
f (0) = 1
2(1 − t)(p2 − p1)
f (1) = 1
2(1 − t)(p3 − p1)
Risolvendo rispetto ai punti di controllo e ponendo s = 1−t
2 otteniamo:
p0 = f(1) − 2
1−tf (0) = a0 + (1 − 1
s )a1 + a2 + a3
p1 = f(0) = a0
p2 = f(1) = a0 + a1 + a2 + a3
p3 = f(0) + 1
s f (1) = a0 + 1
s a1 + 21
s a2 + 31
s a3
27. 2.2. CURVE 17
Figura 2.5: Spline cardinale con diversi valori del parametro di tensione
(Immagini ottenuta tramite la libreria da me sviluppata)
Ci`o porta alla matrice di base:
B = C−1
=
0 1 0 0
−s 0 s 0
2s s − 3 3 − 2s −s
−s 2 − s s − 2 s
Le spline cardinali sono comode in quanto rappresentano uno dei me-
todi pi`u semplici per interpolare un insieme di punti ottenendo una curva
con continuit`a C1 con in pi`u la propriet`a del controllo locale (vedere [12]).
La figura 2.5 mostra l’interpolazione di un insieme di punti tramite spline
cardinale con diversi valori per il parametro di tensione.
2.2 Curve
In questa sezione verranno mostrate le tecniche di approssimazione di curve
che hanno portato negli anni allo sviluppo delle NURBS.
2.2.1 Curve di B´ezier
Le curve di B´ezier sono un tipo di curve la cui forma `e influenzata dal
poligono formato dai punti di controllo, denominato poligono di controllo.
28. 18 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
Le curve di B´ezier utilizzano come funzioni di base i polinomi di Bernstein:
Jn,i(t) =
n
i
ti
(1 − t)n−i
(0)0
≡ 1 (2.3)
Dove con n su k si intende il coefficiente binomiale:
n
k
=
n!
k!(n − k)!
0! ≡ 1
La figura 2.6 mostra le funzioni di base per n = 4.
Una curva con n + 1 punti di controllo, numerati da 0 a n, viene cos`ı
rappresentata:
P(t) =
n
i=0
Bi Jn,i(t) 0 ≤ t ≤ 1 (2.4)
ove Bi indica l’i-esimo punto di controllo. Dato che utilizzano come funzioni
di base i polinomi di Bernstein, alcune propriet`a delle curve di B´ezier sono
note.
ˆ Le funzioni di base sono reali;
ˆ Il grado del polinomio `e pari a n (numero di punti di controllo meno
uno);
ˆ La curva solitamente segue la forma del poligono di controllo;
ˆ Il primo e l’ultimo punto della curva coincidono con il primo e l’ultimo
punto del poligono di controllo;
ˆ La curva `e invariante rispetto a trasformazioni affini4;
ˆ I vettori tangenti al primo e all’ultimo punto di controllo hanno la
stessa direzione del primo e dell’ultimo lato del poligono di controllo.
ˆ La curva rimane all’interno del pi`u grande poligono convesso formato
dai punti di controllo, questa proprit`a va sotto il nome di propriet`a
dell’involucro convesso;
ˆ Si supponga di tracciare una retta in modo che essa “attraversi“ il
poligono di controllo e di conseguenza la curva di B´ezier ad esso asso-
ciata: il numero di intersezioni fra la retta e la curva non `e superiore
al numero di intersezioni fra la retta e il poligono. Questa propriet`a
va sotto il nome di variation-diminishing.
4
Una trasformazione affine `e una trasformazione x → Ax + b, ovvero la composizione
di una trasformazione lineare determinata dalla matrice A e di una traslazione identificata
dal vettore b. Una rotazione `e un esempio di trasformazione affine.
29. 2.2. CURVE 19
Figura 2.6: Funzioni di base per una curva di B´ezier con 5 punti di controllo
2.2.2 Curve B-Spline
Le curve di B´ezier presentano due grossi limiti. Il primo `e rappresentato dalla
relazione fra il numero di punti di controllo e il grado della curva ottenuta.
Per cinque punti di controllo, si otterr`a sempre e comunque una curva di
4° grado. L’unico modo per variare il grado della curva `e di aumentare o
diminuire il numero di punti di controllo. Il secondo problema riguarda una
caratteristica dei polinomi di Bernstein: osservando la figura 2.6 si nota che
ogni funzione Jn,i(u) copre l’intero spazio del parametro; ci`o significa che
se si modifica un singolo punto di controllo, l’intera curva viene influenzata.
Esiste un altro tipo di funzioni di base, le basi B-spline ( da basis spline ) di
cui i polinomi di Bernstein sono un caso particolare. Queste funzioni di base
permettono di decidere il grado della curva indipendentemente dal numero
di punti di controllo e permettono inoltre di avere un controllo locale sulla
curva.
Definizione
Sia P(t) il vettore posizione della curva espresso come funzione del parametro
t. Una curva B-spline `e data da 5:
P(t) =
n+1
i=1
BiNi,k(t) tmin ≤ t < tmax, 2 ≤ k ≤ n + 1 (2.5)
5
Si noti che i punti di controllo sono numerati da 1 a n + 1, e non da 0 a n
30. 20 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
dove Bi rappresenta l’i-esimo punto di controllo, Ni,k(t) `e la funzione di base.
La funzione di base Ni,k(t) di ordine k (grado k − 1) pu`o essere calcolata
tramite la relazione di ricorrenza di Cox-de Boor:
Ni,1(t) =
1 se xi ≤ t < xi+1
0 altrimenti
Ni,k(t) =
(t − xi)Ni,k−1(t)
xi+k−1 − xi
+
(xi+k − t)Ni+1,k−1(t)
xi+k − xi+1
(2.6)
I valori xi sono componenti di un vettore, denominato vettore dei nodi,
con la propriet`a che xi ≤ xi+1. Una propriet`a di queste funzioni di base
`e che per un dato ordine k, la funzione di base Ni,k(t) `e diversa da 0 per
xi ≤ t < xi+k. Quindi ogni punto di controllo influenza solo una zona
limitata della curva, al contrario di ci`o che accade cone le curve di B´ezier.
Una B-spline `e definita come uana spline polinomiale di ordine k in quanto
soddisfa le segueti propriet`a:
ˆ P(t) `e un polinomio di grado k − 1 per ogni intervallo xi ≤ t < xi+1;
ˆ P(t) e le relative derivate di ordine 1, 2, . . . , k − 2 sono continue lungo
l’intera curva.
Una B-spline inoltre, possiede le seguenti propriet`a:
ˆ La somma delle funzioni di base per ogni dato valore del parametro t
`e pari a 1:
n+1
i=1
Ni,k(t) ≡ 1
ˆ Ni,k(t) ≥ 0 per ogni valore del parametro t;
ˆ La curva pu`o essere al massimo di ordine pari al numero di punti di
controllo n + 1, quindi pu`o essere al massimo di grado n.
ˆ La curva possiede la propriet`a variation-diminishing;
ˆ La curva solitamente segue la forma del poligono di controllo;
ˆ Qualsiasi trasformazione affine viene applicata alla curva applicandola
al suo poligono di controllo;
ˆ La curva risiede sempre all’interno dei poligoni convessi creati dai punti
di controllo presi a gruppi di k.
31. 2.2. CURVE 21
Il vettore dei nodi
Dall’equazione (2.6) appare evidente che la scelta del vettore dei nodi
influisce sulle funzioni di base e di conseguenza sulla forma della curva.
Sebbene l’unico requisito per un vettore di nodi sia che valga la relazione
xi ≤ xi+1, di solito vengono utilizzate due categorie principali di vettori dei
nodi, periodici e aperti, entrambi in versione uniforme o non-uniforme.
Un vettore dei nodi uniforme, `e un vettore i cui elementi sono equispa-
ziati, il che significa che xi+1 − xi = c con c costante arbitraria. Alcuni
esempi sono
0 1 2 3 4
−0.2 −0.1 0.0 0.1 0.2
I vettori uniformi in genere iniziano da zero con incrementi unitari fino ad
arrivare a qualche valore massimo, oppure vengono normalizzati all’interno
dell’intervallo [0, 1]. Ad esempio:
[ 0 0.25 0.5 0.75 1 ]
Un vettore dei nodi uniforme periodico, per un dato ordine k porta a
delle funzioni di base con la seguente propriet`a:
Ni,k(t) = Ni−1,k(t − 1) = Ni+1,k(t + 1)
Ogni funzione di base quindi altro non `e che la traslazione dell’altra.
Un vettore dei nodi uniforme aperto ha ai suoi estremi dei valori di nodi
ripetuti un numero di volte pari all’ordine k della B-Spline. I valori interni
sono equispaziati. Alcuni esempi:
k = 2 [ 0 0 1 2 3 4 4 ]
k = 3 [ 0 0 0 1 2 3 3 3 ]
k = 4 [ 0 0 0 0 1 2 2 2 2 ]
Un vettore uniforme aperto `e cos`ı definito:
xi = 0 1 ≤ i ≤ k
xi = i − k k + 1 ≤ i ≤ n + 1
xi = n − k + 2 n + 2 ≤ i ≤ n + k + 1
La figura 2.7 mostra la differenza fra l’uso di un vettore dei nodi aperto e
di un vettore dei nodi periodico, entrambi uniformi.
Quando si utilizza un vettore uniforme aperto e il numero di punti di
controllo `e pari all’ordine della curva, la base delle B-spline si riduce al
polinomio di bernstein. Quindi la curva B-spline ottenuta `e in realt`a una
curva di B´ezier. Il vettore dei nodi ottenuto in questo caso consiste di k zeri
seguiti da k uno.
[ 0 0 0 0 1 1 1 1 ]
32. 22 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
(a)
(b)
Figura 2.7: Due B-spline dello stesso ordine calcolate a partire dagli stessi
punti di controllo: 2.7(a) con un vettore dei nodi uniforme aperto, 2.7(b)
con un vettore nodi uniforme periodico (Immagini ottenute tramite la libreria da
me sviluppata)
33. 2.2. CURVE 23
Un vettore non uniforme `e un vettore in cui l’unico vincolo `e quello
standard: xi ≤ xi+1. Possono esserci valori ripetuti e possono essere equi-
spaziati o meno. Esistono vettori dei nodi non uniformi aperti e non uniformi
periodici, ecco alcuni esempi:
[ 0 0 0 1 1 2 2 2 ] aperto
[ 0 1 2 2 3 4 ] periodico
[ 0 0.28 0.5 0.72 1 ] periodico
Rappresentazione matriciale delle B-Spline
L’equazione (2.5) pu`o essere espressa in notazione matriciale (come illustrato
nella sezione 2.1.6). Questa “traduzione” `e particolarmente semplice se si
assume l’utilizzo di un vettore dei nodi uniforme periodico. Con questo tipo
di vettore infatti, ogni funzione di base `e traslazione dell’altra ed ognuna
di esse ha influenza su esattamente k intervalli. Ci`o significa che tutte le
funzioni di base per 0 ≤ t∗ < 1 hanno la stessa forma, quindi pu`o essere
utile riparametrizzare le funzioni di base all’interno di questo intervallo. Un
punto della B-spline riparametrizzata si ottiene dunque nel seguente modo:
Pj(t∗
) =
k
i=0
N∗
i+1,k(t∗
)Bj+i 1 ≤ j ≤ n − k + i, 0 ≤ t∗
< 1 (2.7)
Dove j indica il tratto della curva. Supponiamo ora di lavorare con una
B-spline di ordine k = 3, le funzioni di base riparametrizzate con t∗ ∈ [0, 1]
sono:
N∗
1,3(t∗) = (1−t∗)2
2
N∗
2,3(t∗) = −2t∗2+2t∗+1
2
N∗
3,3(t∗) = t∗2
2
L’equazione (2.7) diventa quindi:
2Pj(t∗
) = (1 − 2t∗
+ t∗2
)Bj + (−2t∗2
+ 2t∗
+ 1)Bj+1 + t∗2
Bj+2
= t∗2
(Bj − 2Bj+1 + Bj+2) + t∗
(−2Bj + 2Bj+1 + 0Bj+2)+
+ (Bj + Bj+1 + 0Bj+2)
Passando in notazione matriciale si ottiene:
Pj(t∗
) = [T∗
] [N∗
] [B]
=
1
2
t∗2 t∗ 1
1 −2 1
−2 2 0
1 1 0
Bj
Bj+1
Bj+2
34. 24 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
In modo simile `e possibile passare dalle funzioni di base alla notazione
matriciale per k = 4, in questo caso le funzioni di base sono:
N∗
1,4(t∗) = −t∗3+3t∗2−3t∗+1
6
N∗
2,4(t∗) = 3t∗3+6t∗2+4
6
N∗
3,4(t∗) = −t∗3+3t∗2+3t∗+1
6
N∗
4,4(t∗) = t∗3
6
e quindi la forma matriciale `e:
Pj(t∗
) = [T∗
] [N∗
] [B]
=
1
2
t∗3 t∗2 t∗ 1
−1 3 −3 1
3 −6 3 0
−3 0 3 0
1 4 1 0
Bj
Bj+1
Bj+2
Bj+3
Per un generico ordine k, il vettore [ T∗ ] ha la forma:
t∗k−1 t∗k−2 . . . t∗ 1
mentre il generico ingresso della matrice [ N∗ ] = [ N∗
i+1,j+1 ] `e dato dalla
seguente equazione:
N∗
i+1,j+1 =
1
(k − 1)!
k − 1
i
k−1
l=j
(k − (l + 1))i
(−1)l−j k
l − j
0 ≤ i, j ≤ k − 1
(2.8)
2.2.3 Interpolazione 2D tramite B-Spline
Supponiamo di voler interpolare un insieme di punti utilizzando una B-
spline. Il problema `e trovare dei punti di controllo tali da generare una
curva che passi per i punti desiderati. Indichiamo con D il vettore dei punti
della curva noti. Ogni punto Dj corrisponder`a ad un determinato valore del
parametro t, che indicheremo con tj. Per appartenere alla B-spline, i punti
devono soddifare l’equazione (2.5):
D1(t1) = N1,k(t1)B1 + N2,k(t1)B2 + · · · + Nn+1,k(t1)Bn+1
D2(t2) = N1,k(t2)B1 + N2,k(t2)B2 + · · · + Nn+1,k(t2)Bn+1
...
Dj(tj) = N1,k(tj)B1 + N2,k(tj)B2 + · · · + Nn+1,k(tj)Bn+1
35. 2.2. CURVE 25
Dove 2 ≤ k ≤ n + 1 ≤ j. Questo sistema di equazioni pu`o essere scritto in
modo pi`u compatto con la notazione matriciale:
[ D ] = [ N ] [ B ] (2.9)
Dove
[ D ]T = D1(t1) D2(t2) . . . Dj(tj)
[ B ]T
= B1 B2 . . . Bn+1
[ N ] =
N1,k(t1) . . . . . . Nn+1,k(t1)
...
...
...
...
...
...
N1,k(tj) . . . . . . Nn+1,k(tj)
Il valore del parametro tj associato ad ogni punto `e una misura della distanza
tra i punti Di lungo la curva B-spline. Un utile approssimazione di questi
valori utilizza la somma delle distanze euclidee fra ogni coppia di punti.
Nello specifico, per j punti dati, il valore del parametro t associato all’l-esimo
punto `e cos`ı calcolato:
t1 = 0
tl
tmax
=
l
s=2 |Ds − Ds−1|
j
s=2 |Ds − Ds−1|
l ≥ 2
(2.10)
Dove |Di − Di−1| indica la distanza euclidea6 fra il punto i-esimo e il
precedente. Una volta fissati il numero di punti di controllo, l’ordine e il
vettore di nodi che si desidera utilizzare, si pu`o utilizzare l’equazione (2.6)
per calcolare gli elementi della matrice [ N ]. Se si sceglie un numero di
punti di controllo pari al numero di punti appartenenti alla curva (ovvero
n + 1 = j), allora la matrice [ N ] `e quadrata, e i punti di controllo possono
essere ottenuti dall’equazione (2.9) calcolando l’inversa della matrice [ N ].
[ B ] = [ N ]−1
[ D ] 2 ≤ k ≤ n + 1 = j (2.11)
In questo caso, la B-spline ottenuta interpola tutti i punti specificati. Se
invece di un interpolazione si desidera una approssimazione dei punti, basta
scegliere un numero di punti di controllo inferiore al numero di punti della
curva forniti (n + 1 < j). Cos`ı facendo la matrice [ N ] non `e pi`u quadrata
e il sistema di equazioni (2.9) `e sovraspecificato (alcune equazioni non sono
necessarie). Per risolvere il sistema moltiplichiamo entrambi i membri della
(2.9) per [ N ]T , ottenendo cos`ı una matrice quadrata7 che possiamo tentare
6
La distanza euclidea fra due punti a e b `e pari a (ax − bx)2 + (ay − by)2
7
Data una matrice A rettangolare, il prodotto di A per la sua trasposta AT
A `e una
matrice quadrata
36. 26 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
di invertire:
[ D ] = [ N ][ B ]
[ N ]T
[ D ] = [ N ]T
[ N ][ B ]
[ B ] = [[ N ]T
[ N ]]−1
[ D ]
(2.12)
Il tipo di curva ottenuto quindi dipende esclusivamente dalla scelta dei valori
di n+1, k e del tipo di vettore dei nodi. Ad esempio, se si sceglie un vettore
dei nodi aperto con k = n + 1, la curva ottenuta `e una curva di B´ezier.
2.2.4 Curve NURBS
NURBS sta per Non Uniform Rational B-Spline, ovvero B-Spline razionali
non uniformi. Si tratta quindi, di B-Spline che utilizzano funzioni di base
razionali ed un vettore dei nodi non uniforme, aperto o periodico che sia. Ci
sono quindi anche B-Spline razionali uniformi aperte e uniformi periodiche.
Dato che i vettori dei nodi sono stati mostrati nella sezione 2.2.2, qui mi
limiter`o a definire in generale le B-Spline razionali.
Definizione
Una B-Spline razionale `e la proiezione di una B-Spline polinomiale definita
in uno spazio quadridimensionale con coordinate omogenee all’interno di
uno spazio tridimensionale8 (Vedere [19]). Ovvero:
P(t) =
n+1
i=1
Bh
i Ni,k(t) (2.13)
Dove con Bh
i si indicano i punti di controllo omogenei nello spazio quadridi-
mensionale. Ni,k(t) `e l’i-esima funzione di base polinomiale cos`ı come `e stata
descritta dall’equazione (2.6). Per effettuare la proiezione, dividiamo il se-
condo membro dell’equazione (2.13) per le coordinate omogenee, ottenendo
cos`ı la B-spline razionale:
P(t) =
n+1
i=1
BihiNi,k(t)
n+1
i=1
hiNi,k(t)
=
n+1
i=1
BiRi,k(t) (2.14)
8
Si assume che la curva sia definita nello spazio 3D invece che sul piano, il discorso `e
comunque valido anche per il 2D: l’unica differenza, `e che i punti Bi sono identificati da
due coordinate invece di tre.
37. 2.2. CURVE 27
Qui con Bi si indicano i punti di controllo tridimensionali della B-spline
razionale e le
Ri,k(t) =
hiNi,k(t)
n+1
i=1
hiNi,k(t)
(2.15)
sono le basi razionali. Le B-spline razionali contengono le B-spline come
caso particolare: se hi = 1 ∀i, allora la curva ottenuta `e una B-spline non
razionale. Le coordinate omogenee hi sono anche denominate pesi, in quan-
to permettono di decidere quanta influenza deve avere un singolo punto di
controllo sulla curva. La figura 2.2.4 mostra l’effetto che ha il cambio di un
peso su una curva NURBS.
Figura 2.8: Effetto del cambio di un peso su una curva nurbs. Tutte le
curve sono state calcolate con lo stesso vettore dei nodi e gli stessi punti di
controllo, ma `e stato variato il peso del terzo punto: dall’alto verso il basso,
i valori di h3 sono rispettivamente 2, 1.5, 1, 0.75, 0.5, 0.25 e 0.(Immagini
ottenute tramite la libreria da me sviluppata)
Propriet`a
In quanto generalizzazione delle B-Spline non razionali, le B-Spline razionli
condividono con esse alcune propriet`a. Ad esempio:
38. 28 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
ˆ Ogni funzione di base razionale `e maggiore o uguale a 0 per ogni valore
del parametro t, ovvero:
Ri,k(t) ≥ 0 ∀t ∈ [tmin, tmax]
ˆ La somma di tutte le funzioni di base per un dato valore del parametro
t `e pari a 1:
n+1
i=1
Ri,k(t) ≡ 1
ˆ Una B-spline razionale di ordine k ha ovunque una continuit`a di tipo
Ck−2;
ˆ Il massimo ordine che una B-Spline razionale pu`o assumere `e pari al
numero di punti di controllo;
ˆ Le B-spline razionali possiedono la propriet`a variation-diminishing;
ˆ Se tutti i pesi hi sono positivi, la B-spline razionale rimane all’interno
dell’unione degli involucri convessi formati da gruppi di k punti di
controllo.
ˆ Qualsiasi trasformazione proiettiva `e applicata alla B-spline razionale
applicandola ai vertici del poligono di controllo; La curva `e invariante
alle trasformazioni proiettive9. Si noti che questa condizione `e pi`u
forte rispetto a quella espressa per le B-spline non razionali, che sono
invarianti rispetto alle trasformazioni affini.
2.3 Superfici
Rappresentare una superficie in forma parametrica non `e molto diverso dal
rappresentare una curva. La differenza principale `e che sono necessari due
parametri invece di uno:
c1 ≤ u ≤ c2
c3 ≤ w ≤ c4
Una volta fissato l’intervallo di definizione dei due parametri tramite le co-
stanti c1, c2, c3 e c4
10, bisogna esprimere le coordinate dei punti come
9
Una trasformazione proiettiva descrive cosa accade alla posizione percepita di un
oggetto osservato quando il punto di vista dell’osservatore cambia.
10
cos`ı definiti i parametri u e w identificano una forma rettangolare nel piano para-
metrico: sebbene si possa utilizzare una qualsiasi forma nello spazio parametrico per
poi mapparla all’interno dello spazio 3D, continuer`o ad utilizzare per semplicit`a quella
rettangolare.
39. 2.3. SUPERFICI 29
funzioni parametriche:
x = x(u, w)
y = y(u, w)
z = z(u, w)
Specificando un parametro e lasciando variare l’altro si ottiene quella che
viene definita una linea parametrica. Specificando il valore di entrambi
i parametri si ottiene un determinato punto della superficie. Un’ulteriore
differenza `e che ora abbiamo una matrice m×n 11di punti di controllo e non
un vettore. Essi sono numerati nel modo seguente:
B0,m B1,m . . . Bn,m
...
...
...
...
...
...
B0,0 B1,0 . . . Bn,0
Dove m e n sono rispettivamente il numero di punti di controllo lungo
la direzione w e u.
2.3.1 Superfici di B´ezier
Definizione e Propriet`a
Una superficie di B´ezier per un poliedro di controllo con n + 1 punti lungo
la direzione u e m + 1 punti lungo la direzione w `e data dalla seguente
equazione:
Q(u , w) =
n
i=0
m
j=0
Bi,jJn,i(u)Km,j(w) (2.16)
Dove Jn,i(u) e Km,j(w) sono le funzioni di base di Bernstein cos`ı come
descritte dall’equazione (2.3).
Dato che vengono utilizzate le funzioni di base di Bernstein, le superfici
di B´ezier ereditano da esse alcune propriet`a:
ˆ Il grado della superficie in ogni direzione parametrica `e pari al numero
di punti di controllo in quella direzione meno 1;
ˆ La superficie in genere segue la forma del poliedro di controllo;
ˆ La superficie possiede lungo le direzioni u e w rispettivamente una
continuit`a di tipo Cn−1 e Cm−1;
11
In realt`a dato che ogni elemento della matrice contiene le coordinate di un punto, la
matrice `e di dimensioni m × n × 3
40. 30 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
Figura 2.9: Esempio di superficie di B´ezier (Immagine ottenute tramite la libreria
da me sviluppata)
ˆ La superficie `e contenuta nell’involucro convesso creato dal poligono
di controllo;
ˆ La superficie `e invariante rispetto alle trasformazioni affini;
ˆ La superficie non mostra di avere la prorpriet`a variation-diminishing,
essa `e al contempo non definita e non conosciuta.
2.3.2 Superfici B-Spline
Definizione e propriet`a
Una superficie B-spline per un poliedro di controllo con n + 1 punti lungo
la direzione u e m + 1 punti lungo la direzione w `e data dalla seguente
equazione:
Q(u, w) =
n+1
i=1
m+1
j=1
Bi,jNi,k(u)Mj,l(w) (2.17)
Dove Ni,k(u)e Mj,l(w) sono le funzioni di base b-spline cos`ı come descritte
dall’equazione (2.6). Come per le curve, la scelta dei vettori dei nodi [X] e
[Y ] (relativi rispettivamente alle direzioni u e w) influisce sulla forma della
curva. Sebbene di solito si utilizzi lo stesso tipo di vettore per entrambe le
direzioni, `e possibile scegliere tipi diversi. La figura 2.10 a pagina 32 mostra
delle superfici B-Spline che utilizzano diverse combinazioni dei vettori dei
nodi. Dato che vengono utilizzate le funzioni di base B-spline, le superfici
B-Spline ereditano da esse alcune propriet`a, ad esempio:
41. 2.3. SUPERFICI 31
ˆ L’ordine massimo della superficie lungo le due direzioni u e w `e pari
al numero di punti di controllo lungo quella direzione meno uno.
ˆ La continuit`a della superficie `e di tipo Ck−2 lungo la direzione u e
Cl−2 lungo la direzione w.
ˆ La superficie `e invariante rispetto alle trasformazioni affini: una tra-
sformazione affine `e applicata alla superficie applicandola invece al
poliedro di controllo.
ˆ La propriet`a variation-diminishing non `e nota per le superfici B-Spline.
ˆ Se l’ordine della B-spline `e pari al numero di punti di controllo in
entrambe le direzioni parametriche e si utilizzano dei vettori dei nodi di
tipo uniforme aperto, la superficie B-spline si riduce ad una superficie
di B´ezier.
ˆ La regione di influenza di un singolo punto di controllo `e limitata a
±k
2 tratti lungo la direzione u e ± l
2 tratti lungo la direzione w.
ˆ La superficie risiede all’interno degli involucri convessi formati pren-
dendo k, l punti di controllo adiacenti.
Rappresentazione matriciale
Una rappresentazione matriciale per una superficie B-spline periodica `e del
tipo:
Qs,t = [ U∗
][ N∗
][ B∗
s,t ][ M∗
]T
[ W∗
]T
(2.18)
Dove
[ U∗ ] = [ u∗k−1 u∗k−2 . . . u∗ 1 ]
[ W∗ ] = [ w∗l−1 w∗l−2 . . . w∗ 1 ]
con u∗ e w∗ ad indicare i parametri scalati nell’intervallo [0 , 1]. Le matrici
[ N∗ ] e [ M∗ ] sono date dall’equazione (2.8). La matrice [ Bs,t ] rappresenta
una “finestra scorrevole” che consente di calcolare delle sotto-superfici a
partire da un sottoinsieme di k × l punti di controllo. Per superfici B-spline
periodiche calcolate a partire da un poliedro di punti di controllo aperto:
[ B∗
s,t ] = [ Bi,j ] (2.19)
Dove
1 ≤ s ≤ n − k + 2 s ≤ i ≤ s + k − 1
1 ≤ t ≤ m − l + 2 t ≤ j ≤ t + l − 1
(2.20)
Bi,j rappresenta un elemento della matrice dei punti di controllo.
42. 32 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
(a) (b)
(c) (d)
Figura 2.10: Effetto dell’uso di diversi tipi di vettori dei nodi su una su-
perficie B-Spline: 2.10(a) il poliedro di controllo; 2.10(b) entrambi i vettori
di tipo uniforme aperto; 2.10(c) [X] di tipo uniforme periodico e [Y ] di ti-
po uniforme aperto; 2.10(d) entrambi i vettori di tipo periodico. (Immagini
ottenute tramite la libreria da me sviluppata)
43. 2.3. SUPERFICI 33
2.3.3 Interpolazione 3D tramite B-Splines
Come nel caso delle curve mostrato nella sezione 2.2.3, anche le superfici B-
spline possono essere utilizzate per interpolare (o approssimare) un insieme
di punti nello spazio. Organizziamo idealmente i punti da interpolare in
una maglia rettangolare r × s e indichiamo con D la matrice r ∗ s × 3 che li
contiene, con 2 ≤ k ≤ n + 1 ≤ r e 2 ≤ l ≤ m + 1 ≤ s. Il generico elemento
della matrice Di,j sar`a associato a due valori dei parametri, ui e wj. Per
appartenere alla superficie, un punto deve soddisfare l’equazione (2.17). Ad
esempio il punto D1,1 otteniamo:
D1,1(u1, w1) =
N1,k(u1)[M1,l(w1)B1,1 + M2,l(w1)B1,2 + · · · + Mm+1,l(w1)B1,m+1]+
...
Nn+1,k(u1)[M1,l(w1)Bn+1,1 + M2,l(w1)Bn+1,2 + · · · + Mm+1,l(w1)Bn+1,m+1]
Scrivendo quest’equazione per ognuno dei punti da interpolare, si forma un
sistema di equazioni che pu`o essere riscritto in forma matriciale nel seguente
modo:
[ D ] = [ C ][ B ] (2.21)
dove Ci,j = Ni,kMj,l. Come gi`a detto, [ D ] `e una matrice r ∗ s×3, [ C ] `e una
matrice r ∗ s×n ∗ m contenente i prodotti delle funzioni di base, e [ B ] `e una
matrice n ∗ m × 3 delle coordinate dei punti di controllo , rappresentante
l’incognita del problema. Se [ C ] `e quadrata, il problema pu`o essere risolto
direttamente calcolandone l’inversa:
[ B ] = [ C ]−1
[ D ] (2.22)
In questo caso la superficie ottenuta passa per tutti i punti dati. Se in-
vece [ C ] non `e quadrata, il problema `e sovraspecificato, e pu`o essere solo
approssimato:
[ B ] = [[ C ]T
[ C ]]−1
[ D ] (2.23)
I valori dei parametri ui e wj possono essere ricavati in modo simile a quello
descritto dall’equazione (2.10):
u1 = 0
uq
umax
=
q
g=2
|Dg,p − Dg−1,p|
r
g=2
|Dg,p − Dg−1,p|
1 ≤ p ≤ s 1 ≤ q ≤ r (2.24)
44. 34 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
Lo stesso discorso vale per il parametro w:
w1 = 0
wq
wmax
=
q
g=2
|Dp,g − Dp,g−1|
s
g=2
|Dp,g − Dp,g−1|
1 ≤ p ≤ r 1 ≤ q ≤ s (2.25)
dove umax e wmax sono, rispettivamente, i valori massimi dei vettori dei nodi
[X] e [Y ].
2.3.4 Superfici NURBS
Definizione e propriet`a
Una superficie B-Spline razionale con coordinate omogenee quadridimensio-
nale si ottiene dalla seguente equazione:
Q(u, w) =
n+1
i=1
m+1
j=1
Bh
i,jNi,k(u)Mj,l(w) (2.26)
Dove con Bh
i,j si indicano i punti di controllo omogenei, e con Ni,k(u)Mj,l(w)
le funzioni di base b-spline cos`ı come definite dall’equazione (2.6). La pro-
iezione della B-spline non razionale definita nello spazio omogeneo quadri-
dimensionale all’interno dello spazio tridimensionale si ottiene tramite la
seguente equazione:
Q(u, w) =
n+1
i=1
m+1
j=1
hi,jBi,jNi,k(u)Mj,l(w)
n+1
i=1
m+1
j=1
hi,jNi,k(u)Mj,l(w)
=
n+1
i=1
m+1
j=1
Bi,jSi,j(u, w) (2.27)
dove Bi,j indica il punto di controllo tridimensionale di posizione (i, j), e
Si,j(u, w) `e la funzione di base della superficie B-spline razionale:
Si,j(u, w) =
hi,jNi,k(u)Mj,l(w)
n+1
i1=1
m+1
j1=1
hi1,j1Ni1,k(u)Mj1,l(w)
=
hi,jNi,k(u)Mj,l(w)
Somma(u, w)
(2.28)
con
Somma(u, w) =
n+1
i1=1
m+1
j1=1
hi1,j1Ni1,k(u)Mj1,l(w)
`E comodo, sebbene non necessario, assumere hi,j ≥ 0 ∀i, j. Essendo co-
struite a partire dalle funzioni di base B-spline, le superfici B-spline razionali
ereditano da esse alcune caratteristiche, ad esempio:
45. 2.3. SUPERFICI 35
ˆ La somma delle funzioni di base Si,j(u, w) `e pari a uno per ogni valore
dei parametri u e w;
ˆ L’ordine massimo della superficie lungo le due direzioni u e w `e pari
al numero di punti di controllo lungo quella direzione meno uno;
ˆ La continuit`a della superficie `e di tipo Ck−2 lungo la direzione u e
Cl−2 lungo la direzione w.
ˆ La superficie `e invariante rispetto alle trasformazioni proiettive: una
trasformazione proiettiva `e applicata alla superficie applicandola inve-
ce al poliedro di controllo;
ˆ La propriet`a variation-diminishing non `e nota per le superfici B-Spline
razionali.
ˆ Se hi,j = 1 ∀i, j, la superficie B-spline razionale si riduce alla contro-
parte non razionale. Se inoltre k = n+1, l = m+1 e i vettori dei nodi
utilizzati sono di tipo uniforme aperto, la superficie B-spline razionale
si riduce ad una superficie di B´ezier non razionale.
ˆ La regione di influenza di un singolo punto di controllo `e limitata a
±k
2 tratti lungo la direzione u e ± l
2 tratti lungo la direzione w.
ˆ Se hi,j ≥ 0 ∀i, j, la superficie risiede all’interno degli involucri con-
vessi formati prendendo k, l punti di controllo adiacenti.
Una superficie B-spline razionale che utilizza dei vettori dei nodi di tipo
non-uniforme, `e la forma pi`u generale di rappresentazione di una superficie.
La possibilit`a di utilizzare dei pesi (sia positivi che negativi) per i punti di
controllo, permette di disegnare superfici che non possono essere disegnate
con delle superfici b-spline non-razionali. La figura 2.11 mostra gli effetti
della variazione di un peso su una superficie B-spline razionale.
46. 36 CAPITOLO 2. SPLINE, B-SPLINE E NURBS
(a) h2,3 = 0 (b) h2,3 = 1
(c) h2,3 = 5 (d) h2,3 = 10
Figura 2.11: Effetto della variazione di un peso su una superficie B-spline
razionale. Il valore di h2,3 (il punto di controllo pi`u alto) `e stato variato
mentre tutti gli altri sono pari a uno. Nella figura 2.11(a) il punto viene
completamente ignorato, mentre nella figura 2.11(b) la superficie ottenuta `e
una B-spline non razionale. (Immagini ottenute tramite la libreria da me sviluppata)
47. Capitolo 3
Cenni di Python
Ed ora passiamo a qualcosa di completamente
diverso.
Monthy Python
In questo capitolo verranno illustrate alcune peculiarit`a del linguaggio
Python. La scelta di questo linguaggio `e dovuta a diversi fattori: innan-
zitutto, il Python, per essere un linguaggio interpretato, `e molto veloce, `e
multi-piattaforma e permette di risolvere problemi complessi con poche ri-
ghe di codice. Questo linguaggio `e corredato da un vasto insieme di librerie,
come ad esempio NumPy (vedi [4]), che offre funzioni per il calcolo scientifi-
co e l’algebra lineare.L’uso di Python permette l’uso della libreria in diversi
ambiti. Software di modellazione 3D come Blender (vedi [1]) permettono di
includere script in Python, rendendo gli utenti di Blender potenziali uten-
ti della libreria. La versione di Python utilizzata `e la 2.6.5. Sul Python
ci sarebbe da scrivere davvero, davvero molto. Questo linguaggio, creato
da Guido Van Rossum nei tardi anni ’80 con l’intento di correggere buona
parte dei difetti a suo dire presenti negli altri linguaggi di programmazione,
`e diventato ad oggi uno dei linguaggi pi`u apprezzati, trovando applicazio-
ni ovunque. Python `e un linguaggio molto versatile, si possono scrivere
programmi utilizzando il paradigma di programmazione procedurale, quello
orientato agli oggetti, il paradigma funzionale, ed `e anche possibile scrivere
semplici script. Dato che una presentazione approfondita delle funzionalit`a
del Python va oltre gli obiettivi di questo lavoro, di seguito saranno elencate
solo alcune caratteristiche, funzioni ed esempi di sintassi necessari a com-
prendere al meglio il lavoro svolto. Per approfondimenti `e disponibile sotto
licenza GPL 1 il libro [18] che rappresenta l’attuale punto di riferimento
1
Gnu General Public License, http://www.gnu.org/licenses/gpl.html
37
48. 38 CAPITOLO 3. CENNI DI PYTHON
per chi vuole imparare Python. La pagina [10] presenta, oltre ad una breve
guida di stile, alcune tecniche e caratteristiche proprie del linguaggio.
3.1 Caratteristiche basilari
Una delle peculiarit`a del Python `e che esso non utilizza caratteri speciali
per individuare blocchi di codice, ma utilizza il livello di indentazione. Un
codice male indentato non viene interpretato da python. Il linguaggio quin-
di `e pensato in modo da costringere il programmatore a scrivere codice
ordinato. Il Python inoltre utilizza la tipizzazione dinamica, caratteristica
propria di diversi linguaggi di alto livello come Matlab e Ruby. Ci`o vuol
dire che non c’`e vincolo di tipo per una data variabile. Ad esempio2:
1 >>> a = 5
2 >>> print a
3 5
4 # type restituisce il tipo della variabile fornita come parametro
5 >>> type(a)
6 <type ’int’>
7 >>> a = ’Hello World!’
8 >>> print a
9 Hello World!
10 >>> type(a)
11 <type ’str’>
12 >>> a = range(5)
13 >>> print a
14 [0, 1, 2, 3, 4]
15 >>> type(a)
16 <type ’list’>
Ci`o `e reso possibile trattando le variabili in un modo fondamentalmente
diverso da quello utilizzato dai linguaggi di livello pi`u basso. Quando, ad
esempio in C, si dichiarano tre variabili a1, a2, a3, viene riservato in me-
moria dello spazio, la cui quantit`a dipende dal tipo di variabili. Questo
spazio viene riservato anche se le variabili sono tutte e tre dello stesso tipo
e contengono tutte lo stesso valore. Quindi se le tre variabili sono di tipo
intero e hanno tutte valore 10, in memoria vengono riservati 3 × 4 = 12
bytes. Se sono 1’000’000, vengono conservati 4’000’000 di bytes. Python
memorizza il valore una volta, ed assegna a quella zona di memoria tante
“etichette” quante sono le variabili che hanno quel valore. Quindi in python
ad ogni variabile non corrisponde una zona di memoria, una variabile `e un
etichetta che viene assegnata alla zona di memoria che contiene il valore
attuale della variabile.
2
’>>>’ rappresenta il prompt dell’ interprete python
49. 3.2. LE LISTE 39
3.2 Le liste
Uno dei punti di forza del Python risiede nelle strutture che offre. Una di
queste strutture `e la lista. La lista, in Python, `e l’alternativa ai vettori.
Mentre un vettore `e una collezione di oggetti omogenei, ovvero tutti dello
stesso tipo, ed `e di taglia fissata, una lista in Python pu`o contenere oggetti
eterogenei e la sua taglia `e dinamica. Gli elementi di una lista con n elementi
sono numerati da 0 a n − 1. Qualche esempio:
1 >>> l = [] # inizializzo l come lista
2 >>> l.append(4) # aggiungo alla lista il numero 4
3 >>> l
4 [4]
5 >>> l.append(’Hello World!’) # aggiungo la stringa "Hello World!"
6 >>> l
7 [4, ’Hello World!’]
8 >>> l.append(5.9) # aggiungo alla lista il numero 5.9
9 >>> l
10 [4, ’Hello World!’, 5.9000000000000004]
11 >>> l[2] # accedo all’elemento di indice 2
12 5.9000000000000004
13 >>> l[-1] # accedo all’ultimo elemento della lista
14 5.9000000000000004
15 >>> l[-2] # accedo al penultimo elemento della lista
16 ’Hello World!’
17 >>> l[1] # che l’elemento di indice 1
18 ’Hello World!’
19 >>> for item in l: # per ogni elemento della lista
20 ... type(item) # controllo il tipo dell’elemento
21 ...
22 <type ’int’>
23 <type ’str’>
24 <type ’float’>
I comandi dell’esempio precedente mostrano come la lista venga estesa man
mano aggiungendo oggetti. Si noti che una lista `e un oggetto iterabile,
infatti nel ciclo presente alla fine del listato si chiede di verificare il tipo di
ogni elemento della lista, che contiene alla fine un intero, una stringa ed un
numero in virgola mobile. L’indicizzazione degli elementi `e di tipo circolare.
Infatti se si chiede di accedere all’elemento di indice −1 Python restituisce
l’ultimo elemento della lista. Supponiamo ora di voler costruire un vettore
di potenze, tale che p[i] = i2 ∀i ∈ [0, 9]. In linguaggio C la cosa pu`o essere
risolta nel seguente modo:
1 int p[10];
2 int i;
3
4 for( i=0; i < 10; i++){
5 p[i] = i*i;
50. 40 CAPITOLO 3. CENNI DI PYTHON
6 }
In Python:
1 >>> p = [i**2 for i in range(10)]
2 >>> p
3 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
in una sola riga di codice, dove range(n) `e una funzione che genera una lista
contenente i valori dell’intervallo [0, n − 1] e ** `e l’operatore di elevamento
a potenza. In generale per costruire una lista fatta di oggetti determinati
( o selezionati) da una funzione f(n) a partire la una lista di elementi, in
python si pu`o scrivere:
1 risultato = [f(elemento) for elemento in lista]
`E anche possibile introdurre delle condizioni in questa notazione. Ad esem-
pio, supponendo che si voglia inserire nella lista l’ipotetica funzione f se
elemento `e compreso fra 0 e k, 0 altrimenti:
1 risultato = [f(elemento) if elemento >= 0 and elemento < k else 0
for elemento in lista]
Questa notazione pu`o essere annidata in modo da creare liste di liste.
Ad esempio se volessi creare una lista, il cui i-esimo elemento `e una lista
delle potenze ij, con i ∈ [0, 5], j ∈ [0, 9] , posso scrivere:
1 >>> potenze = [[ i**j for j in range(10)] for i in range(6) ]
2 >>> potenze
3 [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
4 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
5 [1, 2, 4, 8, 16, 32, 64, 128, 256, 512],
6 [1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683],
7 [1, 4, 16, 64, 256, 1024, 4096, 16384, 65536, 262144],
8 [1, 5, 25, 125, 625, 3125, 15625, 78125, 390625, 1953125]]
Un’altra comodit`a `e il sistema di slicing, ovvero la possibilit`a di accedere
a “fette” della lista. Alcuni esempi:
1 >>> lista = range(10)
2 >>> lista
3 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
4 >>> lista[2:6] # prendo gli elementi dal 2 al 5
5 [2, 3, 4, 5]
6 >>> lista[-3:] # solo gli ultimi 3
7 [7, 8, 9]
8 >>> lista[:3] # solo i primi 3
9 [0, 1, 2]
10 >>> lista[::2] # un elemento ogni due
11 [0, 2, 4, 6, 8]
12 >>> lista[2:9:3] # un elemento ogni tre dal 2 all’8
51. 3.3. I DIZIONARI 41
13 [2, 5, 8]
Un’altra funzione che ho usato spesso durante il mio lavoro `e stata la
funzione enumerate. Questa funzione `e utile quando nell’iterare una lista,
si vuole utilizzare anche l’indice di posizione degli elementi. Normalmente
una soluzione sarebbe
1 >>> for i in range(len(lista)):
2 ... print i, lista[i]
che utilizza la funzione len, la quale restituisce il numero di elementi con-
tenuti in una collezione. Utilizzando enumerate
1 >>> for i, elemento in enumerate(lista):
2 ... print i, elemento
si ottiene un codice pi`u elegante e facile da comprendere, ed anche pi`u veloce.
Da ricordare inoltre `e l’operatore in, che permette di verificare se un
oggetto si trova all’interno di una collezione:
1 >>> lista = range(10)
2 >>> tupla = (’Python’,’C’,’C++’)
3 >>> 5 in lista
4 True
5 >>> 50 in lista
6 False
7 >>> ’Python’ in tupla
8 True
9 >>> ’Java’ in tupla
10 False
3.3 I dizionari
Un dizionario `e un array associativo, ovvero un array all’interno del quale ad
ogni elemento `e associata una chiave che lo identifica. L’implementazione
che Python offre di questa struttura dati di alto livello `e tanto comoda
quanto semplice da utilizzare. Un classico esempio `e quello della rubrica:
1 >>> rubrica = dict()
2 >>> rubrica[’bianchi’] = 99654321
3 >>> rubrica[’verdi’] = 11123456
4 >>> rubrica[’rossi’] = 10123456
5 >>> rubrica
6 {’bianchi’: 99654321, ’rossi’: 10123456, ’verdi’: 11123456}
Dal dizionario `e possibile ottenere liste iterabili delle chiavi e dei valori
memorizzati:
52. 42 CAPITOLO 3. CENNI DI PYTHON
1 >>> rubrica.keys()
2 [’bianchi’, ’rossi’, ’verdi’]
3 >>> rubrica.values()
4 [99654321, 10123456, 11123456]
e controllare se esiste una determinata chiave:
1 >>> rubrica.has_key(’rossi’)
2 True
3 >>> rubrica.has_key(’python’)
4 False
`e possibile creare un dizionario prendendo chiavi e valori da due liste separate
nel seguente modo:
1 >>> nomi = [’nome1’,’nome2’,’nome3’]
2 >>> valori = [ 5, 10, 15 ]
3 >>> dizionario = dict(zip(nomi,valori))
4 >>> dizionario
5 {’nome1’: 5, ’nome2’: 10, ’nome3’: 15}
zip `e una funzione che a partire da un insieme di liste, restituisce una lista
di tuple, ovvero delle ennuple non modificabili:
1 >>> zip(nomi,valori)
2 [(’nome1’, 5), (’nome2’, 10), (’nome3’, 15)]
3.4 Programmazione orientata agli oggetti
Una classe in python `e definita nel seguente modo:
1 class NomeClasse:
2 def __init__(self, attributo1, attributo2, ..., attributoN):
3 self.attributo1 = attributo1
4 self.attributo2 = attributo2
5 ...
6 def metodo1(self,parametro1, parametro2, ..., parametroN):
7 # corpo del metodo1
8 ...
9
10 oggetto = NomeClasse(valore1, valore2, ..., valoreN)
init `e il metodo costruttore. Il primo parametro che viene passato
ad ogni metodo della classe `e self, che `e un riferimento all’oggetto stesso
(come this in Java). Da notare che non c’`e nessun tipo di riferimento ai
modificatori di accesso per attributi e metodi; questo perch´e python non
prevede alcun tipo di modificatore di accesso, ergo non esistono attributi o
metodi privati. Per indicare che un attributo o un metodo `e privato esiste
53. 3.4. PROGRAMMAZIONE ORIENTATA AGLI OGGETTI 43
una convenzione di nomenclatura, che consiste nell’anteporre un doppio un-
derscore (il carattere ’ ’) al nome dell’attributo o del metodo in questione.
Un piccolo esempio:
1 >>> class Prova:
2 ... def __init__(self,valore):
3 ... self.__attributo = valore
4 ...
5 >>> p = Prova(’ciao’)
6 >>> p.attributo
7 Traceback (most recent call last):
8 File "<stdin>", line 1, in <module>
9 AttributeError: Prova instance has no attribute ’attributo’
10 >>> p.__attributo
11 Traceback (most recent call last):
12 File "<stdin>", line 1, in <module>
13 AttributeError: Prova instance has no attribute ’__attributo’
14 >>> dir(p)
15 [’_Prova__attributo’, ’__doc__’, ’__init__’, ’__module__’]
16 >>> p._Prova__attributo
17 ’ciao’
L’attributo non `e a tutti gli effetti privato, utilizzando la convenzio-
ne di nomenclatura, python cambia il nome dell’attributo / metodo in
NomeClasse NomeAttributo. Questo `e probabilmente uno dei pochi di-
fetti di python, anche se gli utenti di questo linguaggio spiegano la cosa con
un secco “We are all consenting adults here”, ovvero “Siamo tutti adulti
consenzienti”, ad indicare che non dovrebbe essere il linguaggio ad impedire
l’accesso ad un attributo o ad un metodo, ma il buon senso di chi utilizza la
classe. Che si sia d’accordo o meno con questa filosofia, esiste un piccolo truc-
co per proteggere almeno gli attributi. Tra i vari metodi standard che fanno
parte di una classe python, compare il metodo set attribute . Il corpo
di questo metodo contiene il codice da esegure quando si tenta di assegnare
un valore ad un attributo con oggetto.attributo = valore. Il problema
`e che anche self.attributo = valore `e una assegnazione diretta, quindi
dopo aver sovrascritto set attribute anche all’interno della classe stes-
sa non possono assegnare direttamente dei valori agli attributi. Bisogna,
invece, agire direttamente sul dizionario interno alla classe che conserva le
associazioni nome / valore degli attributi. Se ad esempio volessi impedi-
re che l’attributo ’voto’ di un ipotetica classe ’Esame’ venga modificato, la
suddetta classe dovrebbe essere scritta all’incirca nel seguente modo:
1 class Esame:
2 def __init__(self,voto):
3 self.__dict__[’voto’] = voto
4 ...
5 def __set_attribute__(self,nome,valore):
6 if nome == ’voto’:
54. 44 CAPITOLO 3. CENNI DI PYTHON
7 print "Impossibile modificare l’attributo voto"
8 else:
9 self.__dict__[nome] = valore
Per quanto riguarda l’ereditariet`a, Python offre sia l’ereditariet`a singola
che quella multipla. Non tutti i linguaggi possiedono questa caratteristica:
in Java, ad esempio, per ottenere qualcosa di simile bisogna utilizzare il mec-
canismo delle interfacce. Un interfaccia Java per`o contiene solo la firma dei
metodi, non la loro implementazione, quindi ogni volta che si ereditano dei
metodi da un interfaccia essi vanno implementati anche se l’implementazio-
ne `e gi`a disponibile in un’altra classe, il che comporta ridondanza di codice.
Per far ereditare attributi e metodi in Python, la sintassi `e la seguente
1 class ClasseDerivata(Classe1, Classe2, ..., ClasseN):
2 ...
Se un metodo, che per esempio chiameremo metodo1, `e presente in pi`u
di una classe genitore, per decidere quale versione di metodo1 richiamare
bisogna sovrascrivere il metodo nella classe figlia, per poi richiamare espli-
citamente l’implementazione che vogliamo. Supponendo di voler utilizzare
l’implementazione offerta da Classe2:
1 class ClasseDerivata(Classe1, Classe2, ..., ClasseN):
2 ...
3 def metodo1(self, parametro 1, ..., parametroN):
4 Classe2.metodo1(self, parametro 1, ..., parametroN)
Per controllare se un oggetto `e istanza di una classe (o se una variabile
ha un valore di un dato tipo) si pu`o utilizzare l’istruzione isistance, che
prende come parametro l’oggetto da verificare e la classe. La caratteristica
comoda di questa istruzione `e che pu`o essere utilizzata per verificare pi`u
classi alla volta passando come secondo parametro una collezione di classi
(una lista o una tupla). Ad esempio:
1 >>> a = 5
2 >>> isinstance(a,(int,list,dict))
3 True
4 >>> a = ’HelloWorld!’
5 >>> isinstance(a,(int,list,dict))
6 False
7 >>> a = [1,2,3]
8 >>> isinstance(a,(int,list,dict))
9 True
3.5 Le eccezioni
Il modo in cui Python gestisce le eccezioni `e simile a quello utilizzato da altri
linguaggi orientati agli oggetti come C++ e Java. La sintassi `e la seguente:
55. 3.5. LE ECCEZIONI 45
1 try:
2 # codice che potrebbe generare l’errore
3 except TipoEccezione:
4 # codice da esegure se si solleva l’eccezione
5 else:
6 # codice da esegure solo se non stata sollevata
7 # NESSUNA eccezione
8 finally:
9 # codice da eseguire in ogni caso
Ci sono diverse eccezioni standard disponibili con Python, ad esempio:
IOError Eccezione che si solleva in caso di errore di I/O;
TypeError Eccezione che si solleva quando si tenta di applicare un opera-
zione o una funzione ad una variabile del tipo sbagliato (ad esempio
invocare len su una variabile che contiene un intero);
ImportError Eccezione sollevata quando si tenta di importare un modulo
non installato;
ValueError Eccezione sollevata quando si passa ad una funzione un pa-
rametro del tipo giusto ma con un valore inaspettato (ad esempio si
riceve un valore negativo quando ci si aspettava un valore maggiore di
0);
IndexError Eccezione sollevata quando si tenta di accedere ad un indice
fuori limite in una lista.
`E possibile creare eccezioni personalizzate estendendo la classe Exception:
1 class MiaEccezione(Exception):
2 def __init__(self, descrizione):
3 self.descrizione = descrizione
4 def __str__(self):
5 print descrizione
str `e l’equivalente del metodo toString di Java. L’eccezione pu`o essere
sollevata tramite l’istruzione raise:
1 if valore < 0:
2 raise ValueError(’Il valore deve essere > 0’)
Per accedere alla descrizione di una eccezione si pu`o utilizzare l’operatore
di ridenominazione as:
1 try:
2 # codice che potrebbe sollevare l’errore
3 except IOError as descrizione:
4 print "Errore: ", descrizione
56. 46 CAPITOLO 3. CENNI DI PYTHON
3.6 Librerie utilizzate
Nello scrivere la libreria per il calcolo delle nurbs, ho utilizzato principal-
mente due librerie:
NumPy Principalmente per le classi e i metodi per l’algebra lineare ed
altre funzioni di comodo;
Matplotlib Per il plot 2D e 3D.
Le versioni utilizzate sono numpy-1.3.0 e matplotlib-0.99.1.1. Per importare
un modulo in python si utilizza l’istruzione import. `E possibile anche im-
portare singole componenti di un modulo, invece di del modulo intero, ed
eventualmente rinominarle utilizzando l’operatore di rideominazione as. Ad
esempio:
1 # senza utilizzare as
2 import modulo1.classe1
3
4 modulo1.classe1.metodo()
5
6 # utilizzando as
7
8 import modulo1.classe1 as cl
9
10 cl.metodo()
11
12 # posso importo direttamente la classe
13 from modulo1 import classe1
14
15 # oppure posso importare una singola classe e ridenominarla
16 from modulo1 import classe1 as cl
3.6.1 Numpy
Numpy fornisce classi e metodi utili per l’algebra lineare oltre a funzioni
che rendono meno traumatico il passaggio da matlab (come ad esempio
linspace). La classe principale di questa libreria `e ndarray, la quale modella
un array n-dimensionale. Gli attributi pi`u importanti della classe ndarray
sono:
ndim numero di dimensioni dell’array
shape La “forma” dell’array: per una matrice con n righe ed m colonne, il
valore di shape `e (n,m).
size Il numero di elementi dell’array;
dtype Oggetto che descrive il tipo di dati contenuti dall’array.
57. 3.6. LIBRERIE UTILIZZATE 47
Un esempio:
1 >>> import numpy as np
2 >>> a = np.arange(5)
3 >>> b = np.arange(6,10)
4 >>> a
5 array([0, 1, 2, 3, 4])
6 >>> b
7 array([6, 7, 8, 9])
8 >>> import numpy as np
9 >>> a = np.arange(5)
10 >>> b = np.arange(5,10)
11 >>> print a
12 [0 1 2 3 4]
13 >>> print b
14 [5 6 7 8 9]
15 >>> c = np.append(a,b)
16 >>> print c
17 [0 1 2 3 4 5 6 7 8 9]
18 >>> a = np.array([[1,2,3],[4,5,6]])
19 >>> print a
20 [[1 2 3]
21 [4 5 6]]
22 >>> b = np.array([7,8,9])
23 >>> print b
24 [7 8 9]
25 >>> print np.append(a,b)
26 [1 2 3 4 5 6 7 8 9]
27 >>> c = np.append(a,b,axis=0)
28 Traceback (most recent call last):
29 File "<stdin>", line 1, in <module>
30 File "/usr/lib/python2.6/dist-packages/numpy/lib/function_base.py"
, line 3234, in append
31 return concatenate((arr, values), axis=axis)
32 ValueError: arrays must have same number of dimensions
33 # i valori di ndim devono essere uguali per entrambi gli array, per
risolvere basta scrivere
34 # fra parentesi quadre il secondo vettore
35 >>> print np.append(a,[b],0)
36 [[1 2 3]
37 [4 5 6]
38 [7 8 9]]
Un’utile funzione `e append che permette di concatenare due array, permet-
tendo di specficare lungo quale “asse” effettuare l’operazione. Un esempio
pu`o chiarire meglio questa funzionalit`a:
1 # arange funziona come range, ma restituisce
2 # un’istanza di ndarray
3 >>> import numpy as np
58. 48 CAPITOLO 3. CENNI DI PYTHON
4 >>> a = np.array([[1,2,3],[4,5,6]])
5 >>> print a
6 [[1 2 3]
7 [4 5 6]]
8 >>> print a.ndim
9 2
10 >>> print a.shape
11 (2, 3)
12 >>> print a.dtype
13 int32
14 >>> a = np.array([5,7,9],np.double)
15 >>> print a
16 [ 5. 7. 9.]
17 >>> print a.ndim
18 1
19 >>> print a.shape
20 (3,)
21 >>> print a.dtype
22 float64
`E possibile effettuare il prodotto vettoriale fra due vettri a e b tramite
la funzione dot:
1 >>> a = np.arange(5)
2 >>> b = np.arange(10,15)
3 >>> print a
4 [0 1 2 3 4]
5 >>> print b
6 [10 11 12 13 14]
7 >>> print np.dot(a,b)
8 130
9 >>> a = np.array([[1,2,3],[4,5,6],[7,8,9]])
10 >>> print a
11 [[1 2 3]
12 [4 5 6]
13 [7 8 9]]
14 >>> b = np.array([0,1,2])
15 >>> print b
16 [0 1 2]
17 >>> print np.dot(a,b)
18 [ 8 17 26]
3.6.2 Matplotlib
Matplotlib `e una libreria che fornisce funzioni necessarie per eseguire plot
2D e 3D. L’utilizzo `e molto semplice, per il caso 2D:
1 >>> import numpy as np
2 >>> import pylab as pl
59. 3.6. LIBRERIE UTILIZZATE 49
Figura 3.1: Esempio di plot della funzione sin(x) con matplotlib
3 # genera un insieme di valori compresi fra 0 e 2 * pigreco
4 >>> x = np.linspace(0,2 * np.pi)
5 >>> y = np.sin(x)
6 >>> pl.plot(x,y)
7 [<matplotlib.lines.Line2D object at 0xa7c11cc>]
8 >>> pl.show()
L’immagine 3.1 a mostra l’output del codice precedente.
Per quanto riguarda il plot di grafici 3D matplotlib, offre diversi me-
todi tra cui plot wireframe che visualizza solo i lati della figura che si
vuole visualizzare e plot surface che visualizza la superfice completa. Un
esempio:
1 import numpy as np
2 from mpl_toolkits.mplot3d.axes3d import Axes3D
3 import pylab as pl
4
5 X = np.linspace(-10,10)
6 Y = np.linspace(-10,10)
7
8 # Ottengo la griglia di punti
9 X,Y = np.meshgrid(X,Y)
10
11 # Inizializzo l’oggetto immagine che conterr il plot
12 fig = pl.figure(1,dpi=100)
13 ax = Axes3D(fig)
14
15 Z = X**2 - Y**2
16
60. 50 CAPITOLO 3. CENNI DI PYTHON
17 # A scelta si utilizza una delle due
18 ax.plot_wireframe(X, Y, Z, cstride = 1, rstride = 1)
19 ax.plot_surface(X, Y, Z, cstride = 1, rstride = 1)
20
21 pl.show()
cstride e rstride permettono di scegliere la precisione del plot. La figura
3.2 mostra la differenza fra i due tipi di plot.
61. 3.6. LIBRERIE UTILIZZATE 51
(a) Wireframe
(b) Surface
Figura 3.2: Plot della funzione z = x2−y2, in wireframe e superfice completa
63. Capitolo 4
Descrizione delle classi ed algoritmi
implementati
Se, fra dieci anni, mentre state facendo qualcosa
in modo veloce e sporco, improvvisamente mi
immaginerete dietro le vostre spalle mentre vi
dico:“A Dijkstra questo non sarebbe piaciuto”,
quella sar`a l’immortalit`a che mi basta.
Edsger W. Dijkstra
In questo capitolo saranno illustrate le classi che compongono la libreria
che ho sviluppato e gli algoritmi utilizzati. Si noti che la libreria numpy `e
stata importata utilizzando una ridenominazione:
1 import numpy as np
quindi nei fammenti di codice che seguiranno ogni riferimento a np `e un rife-
rimento alla libreria numpy. Buona parte degli algoritmi sono stati adattati
dal testo [19]. Dico “adattati” in quanto gli algoritmi illustrati sul testo, ol-
tre ad essere scritti in uno pseudocodice eccessivamente vicino al linguaggio
C, a volte presentano degli errori e delle incongruenze, probabilmente dovuti
alla loro funzione di linea guida. I codici esposti in questo capitolo non sono
completi, viene mostrato solo ci`o che `e di interesse per la discussione. Per
consultare il codice nella sua interezza fare riferimento all’appendice B. Ho
diviso le classi componenti la libreria in due files: Curve.py e Surface.py.
In Python ogni file `e un modulo, ovvero ogni file pu`o essere incluso come
se si trattasse di una libreria. Volendo, ad esempio, includere nel proprio
codice la classe Nurbs dal file Curve.py, si pu`o scrivere:
1 from Curve import Nurbs
2 c = Nurbs(...)
53
64. 54 CAPITOLO 4. CLASSI ED ALGORITMI
oppure si pu`o importare l’intero file:
1 import Curve
2 c = Curve.Nurbs(...)
e anche utilizzare ridenominazioni
1 import Curve as crv
2 c = crv.Nurbs(...)
4.1 La struttura delle classi
Proseguendo nella lettura si pu`o notare che la gerarchia delle classi non
rispecchia le relazioni presenti tra le curve e le superfici illustrate nel ca-
pitolo 2. Questo perch´e nel decidere la gerarchia delle classi ho cercato
di massimizzare il riutilizzo di codice. Infatti si pu`o notare che metodi che
avrebbero potuto lavorare direttamente sugli attributi della classe utilizzano
invece dei parametri passati in input. Ad esempio, il calcolo delle funzioni
di base b-spline `e lo stesso sia che si tratti di curve che di superfici, quindi
il metodo computeBasis della classe BSpline viene utilizzato nella classe
BSplineSurf, il che rende BSplineSurf classe figlia della classe BSpline.
Sebbene le B-Spline contengano le curve di B´ezier come caso particolare, nel-
l’implementazione di questa libreria non vi `e alcuna relazione di ereditariet`a
fra di esse, in quanto non condividono metodi.
4.2 La classe Points
Sebbene la classe ndarray fornita da numpy basti per modellare un punto,
essa non contiene alcune funzioni necessarie come il calcolo della distanza
euclidea. Ho quindi esteso la classe ndarray definendo la classe Points,
all’interno della quale ho aggiunto i metodi che ho ritenuto pi`u opportuni.
Si noti che dal punto di vista semantico la funzione della classe Points `e
ambigua, in quanto essa `e utilizzata per rappresentare sia un singolo punto
di uno spazio n-dimensionale sia un array di punti. Creare due classi distin-
te sarebbe s`ı stato pi`u corretto, ma una classe rappresentante un array di
punti sarebbe stata composta da un singolo metodo (metodo chordLength
descritto pi`u avanti), un p`o poco per complicare, anche se di poco, la ge-
rarchia delle classi. Ho quindi trovato pi`u vantaggioso modellare la classe
Points in modo che potesse essere utilizzata sia per modellare un singolo
punto che un array di punti.
Costruttore
Il costruttore di questa classe `e diverso da quello illustrato nella sezione 3.4.
Il metodo new `e un metodo che, se presente, viene chiamato prima di
65. 4.2. LA CLASSE POINTS 55
init . Utilizzando questo metodo, che ha come primo parametro la classe
stessa, `e possibile agire direttamente sull’oggetto, piuttosto che assegnare
solo degli attributi.
1 class Points(np.ndarray):
2 def __new__(subclass,data,dtype = np.double):
3 obj = np.asarray(data,dtype).view(subclass)
4 return obj
Il metodo non fa altro che creare un oggetto obj come ndarray. Il metodo
view di ndarray permette di trasformare un’istanza di ndarray in un istanza
di una qualsiasi sottoclasse di ndarray, in questo caso particolare trasforma
obj da istanza di ndarray ad istanza di Points. Per ulteriori dettagli vedere
la documentazione di numpy [4].
Metodo distance
Questo metodo calcola la distanza Euclidea fra il punto rappresentato dalla
classe e un punto p passato per parametro. Ricordiamo che la distanza Eu-
clidea fra due punti n-dimensionali a = (a1, a2, . . . , an) e b = (b1, b2, . . . , bn)
`e:
n
i=1
(bi − ai)2
1 def distance(self,p):
2 return np.sqrt(sum(pow(p-self,2)))
L’implementazione del metodo segue pari passo la definizione, ndarray so-
vrascrive gli operatori aritmetici, quindi p-self restituisce un vettore il cui
i-esimo elemento `e pari alla differenza dell’i-esimo elemento di p con l’i-esimo
elemento di self. pow(x,y) `e una funzione di Python che restituisce xy,
ed sqrt `e la funzione offerta da numpy per il calcolo della radice quadrata.
A questo punto abbiamo un vettore il cui elemento i-esimo `e (pi − selfi)2.
La funzione sum calcola la somma degli elementi del vettore passato per
parametro.
Metodo chordLength
Nei problemi di interpolazione descritti nelle sezioni 2.2.3 e 2.3.3, per stimare
il valore del parametro si `e usata l’equazione (2.10). Il numeratore di quella
equazione rappresenta la somma delle distanze fra ogni coppia di punti. Il
metodo chordLengt calcola la somma delle distanze dal punto di indice i al
punto di indice j. Se non si passano parametri, il metodo calcola la somma
delle distanze di tutti i punti presenti nel vettore.
1 def chordLength(self,i=0,j=None):
66. 56 CAPITOLO 4. CLASSI ED ALGORITMI
2 return sum([self[k].distance(self[k+1]) for k in xrange(len(
self[i:j])-1)])
xrange `e simile a range, solo pi`u efficiente nel caso di intervalli molto ampi.
L’argomento di sum `e una lista creata con la sintassi mostrata nella sezione
3.2, la lista creata `e una lista il cui elemento k-esimo `e la distanza fra il
punto k e il punto k+1, con k che va da 0 fino alla lunghezza della sottolista
contenente gli elementi dall’i-esimo al j-esimo.
Metodo convexComb
Questo metodo molto semplice restituisce la combinazione convessa in u con
un punto p passato per parametro. Nel caso il valore di u ecceda i limiti
dell’intervallo [0, 1].
1 def convexComb(self,p,u):
2 if u < 0 or u > 1:
3 raise ValueError("il parametro u deve essere compreso fra
0 e 1")
4 return (1-u)*self + u*p
Esempio d’uso
Seguono alcuni esempi basilari d’uso della classe Points.
1 >>> import numpy as np
2 >>> from Curve import Points
3 # creazione di un punto di coordinate (3, 5)
4 >>> a = Points([3,5],np.double)
5 >>> a
6 Points([ 3., 5.])
7 # creazione di un punto di coordinate (9, 4)
8 >>> b = Points([9,4],np.double)
9 >>> b
10 Points([ 9., 4.])
11 # calcolo della distanza fra a e b
12 >>> a.distance(b)
13 6.0827625302982193
14 # combinazione convessa tra a e b con u = 0.5
15 >>> a.convexComb(b,0.5)
16 Points([ 6. , 4.5])
17 # creo un vettore di punti che contiene a, b e altri due punti
specificati manualmente
18 >>> punti = Points([a,b,(12,5),(15,9)],np.double)
19 >>> punti
20 Points([[ 3., 5.],
21 [ 9., 4.],
22 [ 12., 5.],
23 [ 15., 9.]])
67. 4.3. LA CLASSE CURVE 57
24 # calcolo la somma delle di stanze fra tutti i punti presenti nell’
array
25 >>> punti.chordLength()
26 14.245040190466598
4.3 La classe Curve
Questa classe contiene attributi e metodi principali comuni a tutti i tipi di
curve implementati in questa libreria.
Costruttore
I principali attributi di una curva sono i punti di controllo necessari per
calcolarla, identificati dal parametro cntrl e il numero di punti della curva
da calcolare, attributo identificato dal parametro npts.
1 class Curve:
2 def __init__(self,cntrl,npts):
3
4 if isinstance(cntrl,str):
5 self.loadFromFile(cntrl)
6 else:
7 try:
8 self.__dict__[’cntrl’] = Points(cntrl)
9 except Exception as detail:
10 raise PyNurbsError("Errore formato punti di controllo
:{0}".format(detail))
11
12 self.__dict__[’npts’] = npts
13
14 self.__dict__[’points’] = np.zeros((self.npts, 2)).view(
Points)
nel costruttore controllo se cntrl `e una stringa tramite l’istruzione isinstance
(vedere la sezione 3.4). In caso positivo, il valore di cntrl viene inteso come
nome del file contenente i punti di controllo, viene invocato quindi il me-
todo loadFromFile per caricare da file i suddetti punti. In caso contrario,
cntrl `e una lista contenente i punti di controllo della curva. Nel blocco
try/except tento di assegnare all’attributo della classe un oggetto di tipo
Points costruito a partire dai punti di controllo passati come parametro.
Metodo calculate
Il metodo calculate `e il metodo che ogni classe dovr`a implementare per
calcolare la curva.
68. 58 CAPITOLO 4. CLASSI ED ALGORITMI
1 def calculate(self):
2 pass
L’istruzione pass permette di dichiarare un metodo lasciando il corpo vuoto.
Metodo plot
Questo metodo consente di effettuare il plot della curva, utilizzando i metodi
della libreria matplotlib. Di default, insieme alla curva, viene visualizzato
il poligono di controllo da cui la curva stessa `e stata generata. Nel caso si
volesse visualizzare solo la curva basta settare il parametro cpgrid a False.
1 def plot(self, cpgrid = True):
2
3 pl.plot(self.points[:, 0], self.points[:, 1])
4
5 if cpgrid:
6 pl.plot(self.cntrl[:, 0],self.cntrl[:, 1],’ro’)
7 pl.plot(self.cntrl[:, 0], self.cntrl[:, 1],’r--’)
8
9 return
Sovrascrittura operatore somma
La sovrascrittura dell’operatore somma consente di concatenare due curve.
Sebbene il metodo sia molto semplice, i miei sforzi si sono concentrati sul
riuscire a rendere questo metodo generico in modo da non doverlo riscrivere
per ogni curva. L’idea di base `e quella di “avvicinare” la seconda curva alla
prima intervenendo sul vettore dei punti di controllo, per poi creare una
nuova curva utilizzando i punti di controllo di entrambe.
1 def __add__(self, c):
2 if not(isinstance(c,type(self))):
3 raise TypeError("Il secondo operando deve essere un
istanza di {0}".format(type(self)))
4
5 other_curve = c.cntrl.copy()
6
7 # calcolo la differenza di posizione fra l’ultimo punto della
prima curva e il primo della seconda
8 diff = self.cntrl[-1] - other_curve[0]
9
10 # traslo i singoli punti della seconda curva
11 for pt in other_curve:
12 pt += diff
13
14 # creo un nuovo insieme di punti di controllo unendo i punti
di controllo delle due curve