Editor for Cubic Curves based on Coin3D Library
Upcoming SlideShare
Loading in...5
×
 

Editor for Cubic Curves based on Coin3D Library

on

  • 548 views

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 ...

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.

Statistics

Views

Total Views
548
Views on SlideShare
548
Embed Views
0

Actions

Likes
0
Downloads
1
Comments
0

0 Embeds 0

No embeds

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Editor for Cubic Curves based on Coin3D Library Editor for Cubic Curves based on Coin3D Library Document Transcript

    • bSpline(), bSplineCalc(float u), bSplineN()................................................................12 bSplineAlloc()................................................................................... .........................13 scale().......................................................................................... .............................13 Altri metodi.............................................................................. .................................13 Struttura dell'albero di scena........................................................................................14 2
    • 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
    • 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
    • ● 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
    • 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 axbyczd=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
    • 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 i1 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
    • ● 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
    • 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
    • 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
    • [ ] 2 −2 1 1 −33 3 −2 −1 2 Hu=[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−1C1i 2hi 2hi 3 3 S  x =  x− x i −1  z  x i− x  z i−1C1i  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 iC1i 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
    • { z 0 =0 hi z i −12 z i  hi hi1 h i1 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−un−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 uik1−u bi , k−1 u b u altrimenti ui k −ui ui k 1 −u i1 i1, k−1 12
    • Le funzioni di miscelamento così costruite consentono un controllo locale: si ha infatti che bi ,1 u=0 se u∉[ui , u i1 ] . Questo, per la definizione della funzione di miscelamento, significa che b i , k u0 if u∈[ui , u ik ] . 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 ≤ipts , 0≤i ptsk  pts−k1 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
    • 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
    • (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