Cubic curves editor written in C + +, using the Coin3D graphics library. The editor allowsto add, change, copy and delete Hermite Spline, B-Spline, Bezier curves. It defines a plan mirroring to create the curves mirror.
2. bSpline(), bSplineCalc(float u), bSplineN()................................................................12
bSplineAlloc()................................................................................... .........................13
scale().......................................................................................... .............................13
Altri metodi.............................................................................. .................................13
Struttura dell'albero di scena........................................................................................14
2
3. Sommario
Il progetto sviluppato consiste in un semplice editor per curve cubiche scritto in C++,
avvalendosi delle librerie grafiche Coin3D. L'editor permette di aggiungere, modificare,
copiare e cancellare curve di tipo Hermite, Spline, B-Spline, Bezier. E' inoltre possibile
definire un piano di mirroring per creare delle curve speculari. Il numero di punti di
controllo, per le curve di tipo Spline, B-Spline e Bezier, non è predeterminato.
Guida all'uso del programma
Utilizzo dell'handle principale
L'handle principale si compone di 3 parallelepipedi. L'utente può agire su ognuno di essi,
una volta che viene attivata la funzionalità associata mediante il tasto “t”. Il movimento
del mouse su un parallelepipedo coincide con uno spostamento lungo l'asse specifico, sia
dell'handle che di tutto ciò che è selezionato in quel momento.
Quando la traslazione è disattivata, è possibile inserire dei nuovi punti per la definizione
di una nuova curva o per la definizione di un piano per il mirroring premendo il tasto
destro mentre il cursore si trova sull'handle. Il nuovo punto inserito ha le coordinate
corrispondenti al centro dell'handle stesso.
Creazione di una curva
La creazione di una curva consiste nell'inseriremento di un numero sufficiente di punti di
controllo. Prima di completare l'operazione di inserimento (tramite la pressione del tasto
“e”), è possibile cambiare il tipo di curva che verrà calcolata. Il tipo di default è Hermite.
Le scelte possibili sono:
● “h” per Hermite
● “p” per B-Spline
● “s” per Spline
● “b” per Bezier
Una volta che il numero di punti di controllo è sufficiente al calcolo di una curva, questa
viene disegnata. Ogni successivo inserimento di un punto di controllo modifica la curva,
quindi questa viene ricalcolata e ridisegnata. Dato che le curve di Hermite implementate
sono definite per 4 punti di controllo, solo i primi quattro punti inseriti saranno
effettivamente usati per il calcolo.
Prima di finalizzare la creazione di una curva è possibile cambiare per un numero
arbitrario di volte il tipo della curva. Una volta premuto il tasto “e” questa possibilità non
è più valida per la curva appena finalizzata.
Modifica di curve
Il programma consente la modifica di una curva mediante l'azione sui punti di controllo o
sul dragger che gestisce lo scaling. E' possibile spostare un qualsiasi numero di punti di
controllo, selezionati mediante “Shift+LClick”, simultaneamente, anche se questi
appartengono a curve diverse. Per spostare una curva con movimento rigido, è possibile
3
4. selezionare uno ad uno tutti i punti oppure selezionare la curva nel suo insieme mediante
“Shift+LClick” sulla curva stessa.
L'operazione di scaling, che avviene in tempo reale, mantiene fisso il punto in cui è posto
il dragger.
E' possibile modificare il grado di una curva b-spline in tempo reale. Per fare questo è
necessario selezionare una (o più) curve di tipo b-spline e premere i tasti PgUp e PgDown.
Ogni pressione aumenta (o diminuisce) il grado della curva di 1, se questa operazione è
consentita dal numero di punti di controllo della curva.
Cancellazione di curve
La pressione del tasto “d” è associata alla cancellazione delle curve selezionate in
quell'istante. Per essere cancellata, una curva deve essere selezionata nel suo insieme,
mediante la combinazione “Shift+LClick” sulla curva stessa.
Mirroring
Il piano che definisce l'operazione di mirroring può essere definito dopo la pressione della
combinazione “Ctrl+m”. Dopo la pressione della combinazione di tasti, il programma si
aspetta l'inserimento di tre punti di controllo (che non possono essere selezionati e
spostati). Per ridefinire il piano è necessario inserire di nuovo tutti e tre i punti.
Una volta che il piano è stato definito in modo soddisfacente, è possibile creare delle
immagini specchiate delle curve selezionate, mediante la pressione del tasto “m”.
Copia di curve
E' possibile creare delle copie di un qualsiasi numero di curve. Per fare questo è
necessario selezionare le curve da copiare e quindi premere il tasto “c”. Le copie vengono
create nella stessa posizione delle curve originali; queste ultime rimangono selezionate,
quindi pronte ad essere traslate.
Descrizione del codice
main/main.cpp
In questo file è definito il punto d'ingresso dell'applicazione, così come le callback per la
gestione degli eventi generati dalla tastiera e dal mouse e per la gestione della selezione
dei punti di controllo e delle curve.
In questo file sono inoltre inizializzate le strutture dati necessarie al funzionamento del
programma.
Funzione main() e variabili globali
In questa funzione vengono inizializzati l'albero di scena, e le strutture dati generali del
programma. In quest'ultima categoria, ad esempio, ci sono:
● la lista delle curve già create (list<CubicCurve*> curves)
● la lista delle curve selezionate, rappresentata dalla lista di chiavi della mappa
selectedCurves (map<CubicCurve *, SoSeparator *> selectedCurves)
4
5. ● il tipo di curva corrente
● le flag per le varie operazioni interattive, rispettivamente per la traslazione, per la
definizione del piano usato nell'operazione di mirroring e per la corretta
inizializzazione della posizione del cursore per la traslazione
(bool translationActive, definingMirrorPlane, setPlanePosition)
● valori per la posizione di base della traslazione. Questi valori vengono inizializzati
quando il cursore del mouse si porta sopra l'handle principale e la traslazione è
attiva. (SbVec3f planePosition0)
● la curva che si sta creando correntemente (CubicCurve * nextcc)
La struttura di base dell'albero di scena viene solo inizializzata all'interno di questa
funzione, dato che essa è definita nella classe SceneGraphInstance, che implementa il
pattern Singleton per permettere una condivisione sicura delle informazioni attraverso
tutte le istanze delle curve create.
Funzione mouseEventGrabber()
Questa funzione viene utilizzata come callback per gli eventi generati dal mouse, sia
quelli generati dalle pressioni dei tasti, sia quelli generati dal movimento del puntatore.
Una pressione del tasto destro sopra l'handle principale (con traslazione disattivata)
comporta l'inserimento di un nuovo punto di controllo per la curva corrente oppure di un
punto per la definizione del piano di mirroring, se questa flag è attivata. In entrambi i casi
il punto inserito ha le stesse coordinate del centro dell'handle.
Una pressione del tasto sinistro su una parte di una curva (poligono di controllo, curva
stessa) in concomitanza con la pressione del tasto Shift, nel caso in cui la flag per la
traslazione sia disattivata, corrisponde alla selezione della curva cubica. Come
conseguenza, tutti i punti di controllo vengono evidenziati e un riferimento all'istanza
dell'oggetto CubicCurve viene inserito come chiave nella mappa delle curve selezionate.
Il valore corrispondente a questa chiave è il separatore corrispondente all'oggetto (di tipo
linea) su cui era il cursore.
Nel caso in cui la flag relativa alla traslazione sia attiva ed il cursore del mouse si
posizioni sopra all'handle principale, è possibile traslare tutti gli oggetti selezionati.
Quando il cursore si posiziona sopra l'handle, la posizione attuale viene registrata nella
variabile planePosition. Questa variabile viene reinizializzata ogni volta che il cursore
viene spostato fuori e poi riposizionato sull'handle, così come il valore della traslazione
relativa all'handle (nelle variabili tx, ty, tz). Grazie a questi valori “di base” e alla
posizione del punto pickato sull'handle è possibile calcolare il valore della traslazione del
cursore, sia rispetto a quando la traslazione è cominciata, sia rispetto all'ultima
traslazione effettuata.
Se il pulsante sinistro del mouse viene premuto mentre il cursore non è sopra alcun
oggetto, la selezione viene azzerata.
Funzione keyboardEventGrabber()
Questa callback gestisce le azioni da compiere in base ai diversi tasti premuti. I tasti
riconosciuti e trattati sono:
● “t”: attiva o disattiva la possibilità di traslare l'handle principale con tutto ciò che è
5
6. selezionato. Per fare questo, viene impostata la flag translationActive.
● “h”, “b”, “p”, ”s”: selezionano il tipo di curva (Hermite, Bezier, B-Spline, Spline
naturale). In ogni caso la curva viene aggiornata e ridisegnata.
● “PgUp”, “PgDown”: nel caso in cui siano selezionate delle curve di tipo B-Spline, la
pressione di uno di questi tasti si traduce nell'aumento (diminuzione) del grado
della curva, se questa operazione è consentita. Le strutture dati della curva
vengono aggiornate e la curva è ridisegnata.
● “c”: permette di copiare le curve selezionate. Le nuove curve sono create usando il
costruttore di copia (CubicCurve(CubicCurve &c)), quindi risultano equivalenti a
quella di partenza. La selezione rimane attiva, quindi le curve originali sono pronte
per essere traslate.
● “d”: permette di cancellare le curve selezionate. Solo curve finalizzate con “e”
possono essere cancellate.
● “Ctrl-m”: attiva la definizione di un piano per il mirroring. L'applicazione, dopo la
pressione di questa combinazione, si aspetta l'inserimento di tre punti (sempre
attraverso l'handle principale).
● “m”: attiva il mirroring per le curve selezionate. I tre punti definiti al passo
precedente (o quelli di default) definiscono un piano (GenericPlane plane) su
cui, per ogni curva selezionata, vengono proiettati i punti di controllo. Replicando la
distanza dal punto di partenza al punto proiettato, si ottiene il punto specchiato
rispetto al piano. In questo modo è possibile creare una curva specchiata rispetto a
quella di partenza.
● “e” : finalizza la curva corrente. I punti di controllo inseriti da questo momento in
avanti saranno associati ad una nuova curva cubica.
Funzioni selMade() e unSelMade()
Queste callback, associate all'istanza della classe SoSelector, aggiornano la
rappresentazione dei punti di controllo delle curve, quando queste vengono selezionate o
deselezionate.
Funzione pickingInputDevice()
Questa è una semplice funzione di controllo, usata per capire quale dei tre cubi che
formano l'handle principale è correntemente usato.
Classe GenericPlane (util/GenericPlane.cpp)
Questa classe, come accennato prima, viene usata per il mirroring di una curva. In uno
spazio tridimensionale, un piano è definito da un'espressione del tipo axbyczd=0
. Per definire completamente un piano di questo tipo è necessario identificare tre punti
pa , pb , pc appartenenti al piano o, in alternativa, un punto pa e un vettore normale al
piano n , con origine nel punto definito. Il vettore [ a , b , c ] , a , b , c∈ℝ a , b , c≠0
rappresenta sempre un vettore normale al piano. Supponendo di avere l'informazione
relativa a tre punti nel piano, si possono quindi determinare le prime tre variabili
calcolando n =[ a , b , c ]= pc − pa × pb− pa . Il quarto parametro può essere determinato
calcolando d=− pa⋅n
6
7. GenericPlane(SbVec3f &pa,&pb,&pc)
Questo costruttore definisce un piano per mezzo di tre punti. Prima di tutto viene
calcolata la norma n (normalizzata a 1), quindi viene calcolato d.
project(SbVec3f &p, &res)
La proiezione di un punto p= x , y , z sul piano può essere vista come
l'applicazione di un vettore v normale al piano nel punto da proiettare, tale che
∣v∣=dist p , .
util/risolTridiag.cpp
In questo file è implementata la funzione che risolve un sistema tridiagonale. La funzione
implementata si riferisce ad un metodo sviluppato specificatamente per questo genere di
sistemi: dato un sistema del tipo Ax =b , la matrice A viene scomposta in due matrici:
A=LU . Nel metodo usato, la diagonale maggiore della matrice U è posta uguale ad 1.
Il sistema da risolvere diventa Lz=b , z=Ux .
Le matrici L, U vengono quindi così inizializzate:
{
l 11 =a1 1
ai−1i
ui−1 i= i=2,, n
l i−1i−1
l i i=ai i −ai i−1 u i−1 i
Dopo questo passaggio è possibile risolvere il sistema Lz=b
{
b1
z 1=
l 11
bi −ai i z i−1
z i= i=2,, n
lii
Conoscendo z è possibile risalire alla soluzione originale del sistema
{ x n =z n
x i =z i−ui i1 x i 1 i=n−1, , 1
Classe SceneGraphInfo (util/SceneGraphInfo.cpp)
Questa classe rappresenta poco più di un contenitore per i riferimenti ai separatori
generici inseriti nell'albero di scena e ad alcune strutture condivise (per risparmiare
spazio in memoria) tra tutte le curve cubiche create. Il costruttore è stato scritto in modo
da implementare il pattern Singleton, cosicchè solo una istanza di questa classe può
essere creata; in questo modo le informazioni generiche rispetto all'albero di scena,
necessarie ad ogni curva cubica, possono essere condivise in modo sicuro.
In particolare, le informazioni condivise riguardano:
● la radice dell'albero di scena e i separatori lineSeparator, pointSeparator,
inputSeparator
7
8. ● Il selezionatore (istanza della classe SoSelector)
● la matrice per il calcolo delle curve cubiche di tipo Hermite (con 4 punti di
controllo)
● i punti che definiscono il piano di mirroring (non necessario per le curve, ma
inserito in questa classe per pulizia)
● i riferimenti alla finestra principale e all'ExaminerViewer
Classe CubicCurve (curves/CubicCurve.cpp)
Questa classe rappresenta la parte più corposa del progetto. Ogni istanza di questa
classe è composta da numerosi attributi.
● curveType rappresenta il codice del tipo di curva attualmente impostato
● bezierBinomial è un vettore di coefficienti binomiali che vengono salvati per non
dover essere ricalcolati ad ogni aggiornamento della curva
● bSplineT è il vettore dei nodi per una curva di tipo B-Spline
● bSplineK è il grado della curva, se questa è di tipo B-Spline
● spline* sono vettori necessari al calcolo di una curva di tipo Spline
● fourConditions è un contenitore usato nel calcolo delle curve di Hermite
● boundingBoxPoint è la lista di punti che definiscono il bounding box della curva
● sgi è il riferimento all'oggetto che contiene le informazioni generiche sull'albero di
scena.
● Step rappresenta la distanza fra due punti consecutivi in cui la curva viene
calcolata. Questa distanza è relativa al parametro u
● sis è l'oggetto (SoLineSet) che rappresenta la curva cubica, rappresentata con
colore rosso
● bbils è l'oggetto (SoIndexedLineSet) che rappresenta il bounding box della curva
cubica, di colore giallo
● controlSis è l'oggetto (SoLineSet) che rappresenta la linea (di colore blu) che
congiunge i punti di controllo della curva
● ptsTr è la lista delle coordinate dei punti di controllo per la curva, in ordine di
inserimento. Anche se le informazioni sono ridondanti con quelle contenute nella
lista translations, per comodità e leggibilità le informazioni sono state duplicate.
In realtà le liste si riferiscono agli stessi valori, dato che nella lista ci sono i
riferimenti (puntatori) agli oggetti veri e propri
● spheres è la lista di oggetti di tipo SoSphere che rappresentano i punti di controllo
● translations è la lista di oggetti di tipo SoTranslation che rappresentano i punti di
controllo
● graphicPts è la struttura dati di base su cui viene costruito l'oggetto che
rappresenta la curva cubica
● lineSeparator e pointSeparator rappresentano le radici per i sottoalberi relativi
alla curva. In Figura 2 si possono vedere quattro istanze di ciascuno (i punti sotto i
nodi line e point)
● scaleSeparator è il separatore sotto cui viene posto il dragger per riscalare la
8
9. curva cubica. Per non permetterne la selezione, la sua posizione nell'albero di
scena è quella di nodo figlio del separatore line
● tmax rappresenta il punto del bounding box con le coordinate maggiori. In questo
punto viene posto il dragger per lo scaling
● scaler è il dragger per lo scaling. Una modifica al campo scaleFactor si traduce
in una chiamata alla callback scale(), quindi in un ridimensionamento della curva
● min, max rappresentano le coordinate dei punti minimo e massimo del bounding
box della curva
init()
Questo metodo viene richiamato da tutti i costruttori. Le sue funzioni sono quelle di
istanziare tutti i separatori, le liste e le altre strutture dati appena presentate e di
aggiornare correttamente l'albero di scena.
refresh()
Questo metodo rappresenta il cuore della classe, in quanto controlla la computazione per
l'aggiornamento e la visualizzazione di tutte le parti della curva (poligono di controllo,
bounding box, curva stessa).
La linea congiungente i punti di controllo viene sempre computata. Per la visualizzazione
viene usata un'istanza della classe SoLineSet. In base al tipo di curva, viene poi
chiamato il metodo principale per l'aggiornamento dell'oggetto che rappresenta la curva
interpolante. Dopo questo passaggio viene calcolato e visualizzato il bounding box. Gli
ultimi due passaggi vengono compiuti solo se il numero di punti di controllo è sufficiente
alla computazione corretta della curva.
Il calcolo del bounding box avviene partendo dalle informazioni recuperate da un'istanza
della classe SoGetBoundingBoxAction, che viene applicata al separatore lineSeparator
(che contiene le tre diverse tipologie di linea per la rappresentazione di una curva
cubica). Con queste informazioni viene impostato un oggetto di tipo SoIndexedLineSet,
che rappresenta il bounding box stesso.
Con le informazioni relative ai punti minimo e massimo del bounding box viene inoltre
calcolata la rotazione relativa al dragger per lo scaling. La direzione dell'asse principale di
questo oggetto, posto nel punto massimo, è infatti “(max – min)”. La rotazione
complessiva viene vista come la composizione di due rotazioni, la prima attorno all'asse y
e la seconda relativa ad all'asse z. Per la prima si considerano quindi solo le coordinate
x,z e si calcola la rotazione nel punto max, per ottenere la corretta direzione verso il
punto min. Questo procedimento si può meglio capire facendo riferimento alla Figura 1.
9
10. Figura 1
La seconda rotazione viene calcolata nello stesso modo. Per ottenere la corretta ampiezza
dell'angolo, però, i calcoli vanno effettuati nel sistema di riferimento ruotato.
hermite4Points()
Questo metodo aggiorna la curva trattandola come una curva di Hermite. I primi quattro
punti di controllo inseriti vengono usati per definire le condizioni che permettono di
calcolare i punti della curva. In particolare, il secondo ed il terzo punto rappresentano
l'inizio e la fine della curva. Il primo ed il quarto, rispettivamente, le derivate nel punto di
partenza ed in quello finale. In questo modo si definiscono quattro condizioni che
permettono di descrivere completamente la curva
{
c1 =p1−p 2
c2 =p2
c3 =p3
c4 =p4−p 3
Agendo separatamente su ogni coordinata e iterando il processo di calcolo sul parametro
u, u∈[0,1] , è possibile calcolare (con il metodo hermiteCalc()) un'approssimazione
della curva di Hermite identificata dalle quattro condizioni espresse.
hermiteCalc(float u)
Con questo metodo, supponendo che le quattro condizioni relative ad una curva di
Hermite siano state correttamente impostate, è possibile calcolare una delle tre
coordinate relative al parametro u. Il procedimento consiste nel pesare le quattro
condizioni per mezzo dei polinomi di Hermite
{
3 2
h1 u=2u −3u 1
3 2
h2 u=−2u 3u
h3 u=u 3−2u2 u
h4 u=u 3−u2
In questo modo la curva di Hermite può essere definita come
10
11. [ ]
2 −2 1 1
−33 3 −2 −1
2
Hu=[u u u1] [c1 c2 c3 c4 ]T
0 0 1 0
1 0 0 0
Nell'implementazione questo calcolo viene ripetuto tre volte per ogni punto, dato che per
comodità le condizioni si riferiscono ad una sola coordinata.
naturalSpline()
Questo metodo interpola la curva calcolata con il metodo delle spline cubiche naturali.
Dati n+1 punti da interpolare, la curva S viene genericamente scritta come
{
S0 x x ∈[ x 0, x 1 ]
S x = S1 x x ∈[ x 1, x 2 ]
Sn−1 x x∈[ x n−1 , x n ]
Si impongono le condizioni di continuità: la curva S deve essere continua in [u0, un] e deve
essere derivabile due volte (anche le derivate, nei punti di congiunzione delle Si, devono
essere continue). Le derivate seconde vengono descritte come delle funzioni lineari,
quindi le derivate prime sono al più quadratiche, e quindi le funzioni Si sono al più
cubiche.
II x −x i −1 x i− x
Si x = zi z i −1 , hi = x i−x i−1
hi hi
2 2
I x −x i −1 x i −x
S x =
i z i z i−1C1i
2hi 2hi
3 3
S x =
x− x i −1 z
x i− x z i−1C1i x− x i C 0i
i i
6hi 6hi
E' possibile determinare le funzioni C 1i e C0i ragionando su come si comporta la funzione S i
negli estremi in cui è definita.
h2
i h2
i
S i x i−1 = z i −1 C0i=y i−1 C0i =y i−1 − z i−1
6 6
2
hi yi− y i−1 hi
S i x i = z iC1i hi C0i= y i C1i = − z i−z i −1
6 hi 6
Ponendo l'uguaglianza delle derivate prime negli estremi degli intervalli si ottiene la
definizione del sistema che permette di calcolare le zi. Questo sistema può essere
riscritto in forma matriciale, riconoscendo nella matrice dei coefficienti la forma
tridiagonale
11
12. {
z 0 =0
hi z i −12 z i hi hi1 h i1 z i 1=6
yi 1 −y i
hi 1
−
y i− y i−1
hi
z n =0
E' necessario risolvere tre sistemi, uno per ogni dimensione. La risoluzione di questi tre
sistemi è demandata alla funzione risolTridiag().
Una volta che le zi sono state calcolate, il metodo compie un ciclo sul parametro u per il
calcolo dei punti interpolanti la curva cubica. Il calcolo di ogni singolo punto è demandato
alla funzione naturalSplineCalc().
naturalSplineCalc(float u)
Questa funzione implementa semplicemente l'espressione per le funzioni Si(u) descritte al
punto precedente. Il calcolo presuppone che i sistemi relativi alla curva siano stati
correttamente risolti. Il risultato è il punto nello spazio 3D relativo al parametro u.
bezier(), bezierCalc(float u)
Questi due metodi, raggruppati in questa spiegazione per la loro relativa semplicità,
implementano la formulazione generica di una curva di Bezier.
La formulazione generica (per un qualsiasi numero di punti di controllo superiore a uno) è
n
i=0 i
B u=∑ n P i 1−un−i ui , u∈[ 0,1]
Il calcolo dei fattori binomiali che compaiono nella formula avviene solo quando alla
curva viene aggiunto un punto di controllo; se questo non è il caso, i valori vengono
recuperati dal vettore bezierBinomial, inizializzato dal metodo fillBezierBinomial().
Il metodo bezierCalc(), che implementa la suddetta formula, restituisce le coordinate di
un punto nello spazio 3D corrispondente al parametro u. Il metodo bezier(), d'altro
canto, si occupa di inserire tutti i punti calcolati nell'oggetto sis, che rappresenta la
curva interpolante.
bSpline(), bSplineCalc(float u), bSplineN()
Questi tre metodi implementano la formulazione generica di una curva B-Spline. Questo
tipo di curva è caratterizzato dalla formulazione
n
P u=∑ p i bi , n u
i=0
dove le funzioni di miscelamento sono definite come
{
1 se k=0∧u∈[u i , ui 1 ]
0 se k=0∧u∉[u i , ui 1 ]
b i , k u=
u−ui uik1−u
bi , k−1 u b u altrimenti
ui k −ui ui k 1 −u i1 i1, k−1
12
13. Le funzioni di miscelamento così costruite consentono un controllo locale: si ha infatti che
bi ,1 u=0 se u∉[ui , u i1 ] . Questo, per la definizione della funzione di miscelamento,
significa che b i , k u0 if u∈[ui , u ik ] .
Quando il grado della curva è massimo, quest'ultima è identica ad una curva di Bezier.
Il vettore dei nodi della curva è calcolato dal metodo bSplineAlloc().
bSplineAlloc()
Questo metodo viene richiamato quando ad una curva B-Spline viene aggiunto un punto
di controllo, oppure quando il grado della curva viene modificato. In particolare, questo
metodo inizializza il vettore dei nodi della curva, seguendo la formulazione
{
0 i k
u i= i−k 1 k ≤ipts , 0≤i ptsk
pts−k1 i pts
pts è il numero di punti di controllo definiti e ui rappresenta l'i-esimo nodo.
scale()
Questa funzione (non è un metodo della classe CubicCurve, ma una funzione friend) è la
callback che viene richiamata ogniqualvolta lo stato del dragger per lo scaling viene
modificato. Lo stato è rappresentato dal primo valore contenuto del campo scaleFactor
dell'oggetto, che di default è impostato a 1.
Un valore diverso dal valore di default corrisponde ad un'operazione di scaling
proporzionale al valore stesso. Il processo mantiene fisso il punto in cui si trova il dragger
(identificato con max). La procedura implementata per ottenere questo risultato può
essere scomposta in passi
1. per ogni punto di controllo viene calcolata la distanza rispetto a max
2. questa distanza viene moltiplicata per il nuovo fattore di scaling, calcolato come il
rapporto tra nuovo il dato recuperato dal dragger e il vecchio fattore di scaling
(mantenuto in memoria)
3. il risultato rappresenta il nuvo valore per la traslazione dei punti di controllo
4. quando ogni punto di controllo è stato modificato, la curva viene ridisegnata
Lo stato del dragger non viene modificato dalla procedura descritta.
Il risultato di questa funzione corrisponde ad una operazione di scaling descritta dalla
matrice S
[ ]
t 0 0 0
0 t 0 0 new
S= , t=
0 0 t 0 old
0 0 0 1
13
14. Altri metodi
La classe CubicCurve contiene altri metodi, che non riguardano specificatamente la
computazione dell'interpolazione delle curve cubiche, ma che sono necessari per il buon
funzionamento del programma.
● multSb4f() calcola il prodotto scalare fra due vettori di quattro elementi
● reset() prepara la curva ad essere ricalcolata per intero, dopo un cambio di tipo o
un inserimento di un nuovo punto di controllo
● select() fa sì che l'istanza della classe SoSelector inserita nell'albero di scena
visualizzi il box di selezione attorno ai punti di controllo quando una curva cubica è
selezionata con la combinazione “Shift + Lclick”
● setBSplineDegree() imposta il grado della curva (implicitamente si assume che
essa sia di tipo B-Spline). Vengono compiuti dei controlli affinchè il grado sia
compatibile con la situazione attuale della curva.
● showScaler() aggiunge il dragger per lo scaling all'albero di scena, facendo sì che
esso venga visualizzato
● fact() e binom() vengono utilizzati per il calcolo del coefficiente binomiale
● getMinPointCounter() ritorna il numero minimo di punti necessari per poter
calcolare una curva cubica, in funzione del tipo (2 per B-Spline e Bezier, 4 per
Spline cubiche ed Hermite)
Struttura dell'albero di scena
In Figura 2 è possibile vedere una rappresentazione schematica dell'albero di scena. Sono
esplicitati gli otto oggetti principali, con gli elementi secondari rappresentati con dei
cerchi.
Figura 2: schema della struttura dell'albero di scena
1. root: la radice dell'albero.
2. pcam: un'istanza della classe SoPerspectiveCamera.
3. interaction: il gestore delle callback generate dagli eventi di tastiera e mouse.
4. mirrorPlaneSeparator: separatore per i punti che definiscono il piano usato
nell'operazione di mirror.
5. lineSeparator: separatore per i nodi che definiscono le linee che compongono le
varie curve cubiche. A colori diversi corrispondono dei separatori diversi, uno per
ogni curva. Ognuno di questi separatori ha come nodi le tre diverse linee che
compongono una curva: la curva vera e propria, il bounding box e il poligono di
controllo.
6. selector: oggetto per la gestione della selezione e highlighting degli oggetti grafici
14
15. (istanze di SoSphere) che rappresentano i punti di controllo.
7. pointSeparator: seguendo lo stesso principio del lineSeparator, viene introdotto un
diverso separatore per ogni curva presente.
8. inputSeparator: ha come nodi i tre cubi che definiscono l'handle principale.
Nella descrizione e nello schema si sono volutamente omessi dei nodi (come le istanze di
SoTranslation) per questioni di leggibilità, cercando di non inficiare la completezza della
descrizione.
Nello schema presentato, come si può capire dalla struttura dei sottoalberi in
lineSeparator e pointSeparator, sono presenti quattro diverse curve cubiche. Le istanze
delle linee e dei punti associati alla stessa curva sono rappresentate con lo stesso colore
(nell'ordine: verde, blu, rosso, arancio).
Dato che il separatore mirrorPlane ha dei nodi sotto di esso, nell'esempio presentato è
stato definito dall'utente un piano per il mirroring diverso da quello di default (piano xy). I
punti che definiscono il piano, infatti, sono rappresentati nell'albero di scena solamente
quando essi vengono inseriti manualmente.
15