SPLINE disegno di linee e superfici definite da un insieme di punti dati, approssimanti una linea (o superfice) ideale definita dai punti dati, linea interpolante i punti... spline per modellare una superficie non facilmente esprimibile con facce piane (triangoli) o con una superficie quadrica o cubiche si usa una tecnica di approssimazione: dato un insieme di punti si puo' definire una curva ottenuta da spezzoni di curve polinomiali che : approssima i punti dati (passa vicino ad essi) passa per i punti dati (interpolazione) spline in 2D: dato un insieme di punti si puo' definire una curva ottenuta da spezzoni di curve polinomiali che passa oppure approssima i punti dati (interpola oppure passa vicino ad essi) la parola spline originariamente indicava una asticella flessibile usata dagli artigiani per ottenere una curva interpolante alcuni punti dati ( l' asticella veniva ancorata a questi punti, e per la sua flessibilita' essa "passava" per questi punti) oggi una linea spline si intende una linea approssimante ottenuta da spezzoni di polinomiali (normalmente cubiche) CON IL VINCOLO di essere continua e di avere la derivata prima (talvolta anche la seconda) continue SPLINE definizione della parola inglese : spline |splīn| |splaɪn| |splʌɪn|noun 1 a rectangular key fitting into grooves (scanalature) in the hub (centro) and shaft (asse) of a wheel, esp. one formed integrally with the shaft that allows movement of the wheel on the shaft.・ a corresponding groove in a hub along which the key may slide. 2 a slat (asticella, es. parte di una tenda veneziana) a flexible wood or rubber strip used esp. in drawing large curves 3 (also spline curve) Mathematics a continuous curve constructed so as to pass through a given set of points and have a certain number of continuous spline dati 4 punti si puo' definire una curva ottenuta da spezzoni di curve polinomiali che * passa per i punti dati (interpolazione), nella figura a destra a destra una linea interpolante (Bezier) 4 punti, oppure che * approssima i punti dati (passa vicino ad essi), nella figura a sinistra una linea approssimante (BSpline), spline dati n punti si puo' definire una curva ottenuta da spezzoni di curve polinomiali che * passa per i punti dati (interpolazione) oppure che * passa vicino i punti dati (approssimazione) in figura una linea approssimante (BSpline), spline in 3D: per modellare una superficie non facilmente esprimibile con * facce piane (triangoli) o con * superfici quadriche o cubiche (una faccia, una sagoma d'automobile) si usa una tecnica di approssimazione: dato un insieme di punti si puo' definire una superficie ottenuta da spezzoni di superfici polinomiali che * passano per i punti dati (interpolazione) oppure * approssimano i punti dati (passa vicino ad essi) nella pagina seguente un esempio di superficie costruita con frammenti di superfici cubiche approssimanti un insieme di punti dati (punti di controllo) spline nello spazio tridimensionale in figura a destra una superficie ottenuta con frammenti di superfici cubiche approssimanti dei punti dati (non visualizzati) disegnata in tre modalita' diverse * a linee * a punti * a triangoli pieni i dati di partenza sono un reticolo di 4 x 4 punti, ( x[i]=x0+i*dx, y[j]=y0+j*dy, e z[i,j]=f(x[i],y[j]) ) (nella pagina seguente sono segnati anche i punti di controllo); Vedremo in seguito il caso 3D spline in figura una superficie approssimante un insieme di 4x4 punti dati (punti di controllo) - nota: i punti sono a distanza dx e dy costante, come si vede a destra ma cambia z=f(x,y) [sotto] vista da sopra nota: due punti di controllo stanno sotto la superfice vista di fianco (anche qui alcuni punti di controllo non sono visibili) esempio di modello 3D di una faccia questa figura "Oscar" (derivata dal modello "Geoface" da rete) e'stata usata per simulare i movimenti dei muscoli del viso in sincronia con un parlato sintetico di un programma che legge ad alta voce un testo scritto e fornisce tramite la faccia anche un aiuto visivo (Righetti, tesi apr.2008) la "resa" della superficie del viso e' fatta con la libreria OpenGL usando metodi di illuminazione e di tessitura standard; (lavoro iniziato da uno studente del corso EGD del 2006) spline ritorniamo al caso 2D: vediamo l' idea di base: dati ad es. 3 punti come in figura a fianco sopra posso definire una linea continua che approssimi oppure interpoli i tre punti; aggiungendo un punto, in totale 4 punti dati, la linea e' ottenuta da tre spezzoni, raccordati in modo da essere continui e avere la stessa tangente nel punto di raccordo (derivata prima continua) in basso, 5 punti dati, linea formata da quattro spezzoni di curve cubiche raccordate x x x x x spline vediamo l'idea di base: o o 4 punti dati (p.ti di controllo) linea fatta da tre spezzoni raccordati (continui e con la stessa tangente nel punto di raccordo o; importante: la linea (cubica) e' tracciata come una polyline, ovvero come una linea spezzata di piccoli segmenti di retta !! i vertici dei singoli segmentini (i punti neri in fig.) sono calcolati (funzione cubica) dai punti dati vicini (in figura, dai primi tre punti dati); i segmentini sono "abbastanza" corti da non vederli, spline con piu' punti di controllo posso ottenere una curva generica, ad es. come in figura, ottenuta in forma parametrica; si noti come i punti di controllo funzionano da "attratori" della linea complessiva; spostando un punto la linea "segue" localmente lo spostamento. la linea viene approssimata con una spezzata a tratti (segmenti) tanto corti da apparire una linea curva continua, e qui e' scelta in modo da passare vicino ai punti dati (punti di controllo) modellazione con curve e superfici interpolanti Due mini esempi di figure costruite con interpolanti: con estensione a 3D (e con un po' di software) si possono ottenere immagini altrimenti difficilmente ottenibili, e che sono facilmente editabili (spostando un o piu' punti di controllo). Nota che a sinistra la curva passa vicino per i punti di controllo, a destra passa per i punti di controllo (algorit.diversi) L' algoritmo di De Casteljau Con 3 punti ho una curva approssimante con due passi di interpolazione lineare: sul segmento P1-P2 ho il punto A = P1*(1-u) + P2*u, (A==P1 se u=0, A==P2 se u=1), sul segmento P2-P3 ho B = P2*(1-u) + P3*u; (qui u vale 0,3) cosi' da tre punti passo a due; ripeto l'interpolazione per il segmento A-B, e ho P = A*(1-u) + B*u NB: P, punto della curva, comincia da P1 per u=0 (u=0 -> A==P1 e B==P2) e alla seconda interpol. tra A e B, P==A==P1) e poi percorre [u da 0 a 1] la curva fino a P3 (con u=1, P==B==P3); per disegnare la curva si approssima con nsegmenti di retta. La fig.e'data da: www2.mat.dtu.dk/people/J.Gravesen/cagd/decast.html L' algoritmo di De Casteljau o o o o u=0.3 a sinistra, u=0.5 in mezzo, u=0.7 a dest.; qui sono dati 4 punti di controllo o, i passi di interpolazione sono tre, uno piu' di prima. www2.mat.dtu.dk/people/J.Gravesen/cagd/decast.html NOTA: l' algoritmo di De Casteljau e' molto semplice (vedremo) ma usa tutti i punti di controllo per fare un punto generico della curva, per tale motivo non e' molto usato; il progr. EGD_4B non riesce a disegnare una curva con Casteljau se vi sono piu' di 20 punti controllo. L' algoritmo di De Casteljau Ancora due esempi sull' algoritmo De Casteljau: in figura con 4 punti di controllo (rosso e verde), un punto della curva segnato in giallo, e disegnati i segmenti interpolanti al primo e al secondo livello interpolazione, con il fattore di controllo u (qui t) a www2.mat.dtu.dk/people/J.Gravesen/cagd/decast.htm valore 0.5 L' algoritmo di De Casteljau ancora, curva con 9 pti di controllo e con 8 segmenti, e con 8=9-1 passi di interpolazione lineare che vanno da 9 pti (8 seg) (1) a 8 pti (7 seg) (2) a 7 pti (6 seg) (3) a 6 pti (5 seg) (4) a 5 pti (4 seg) (5) a 4 pti (3 seg) (6) a 3 pti (2 seg) (7) a 2 pti (1 seg) (8) a 1 punto o che sta sulla curva L' algoritmo di De Casteljau - //nota:struct tpunto{float x;floaty;}; void casteljau ( float PTabX[ ],float PTabY[ ], int PointCount, int nseg ) { int k; float kf,u; tpunto punti[NN]; tpunto p; if(PointCount>NN) exit(71); for (k=0; k<PointCount; k++) { // cambia formato punti[k].x = PTabX[k]; // pti punti[k].y = PTabY[k]; } // for glBegin( GL_LINE_STRIP ); glVertex2f( punti[0].x, punti[0].y ); // 1.o for (k = 0; k<= nseg; k++ ){ kf = k; u = kf/nseg; //<< nota fattore u !! p = castel( punti, 0, PointCount -1, u); // ricorsiva Pointcount volte (segue) glVertex2f( p.x, p.y ); // punto da fattore u } // for k glEnd(); } // casteljau L' algoritmo di De Casteljau ... struct tpunto { float x; float y; }; ... tpunto castel(tpunto punti[ ],int i,int NumPun, float u) { tpunto p,p1,p2; if( NumPun == 1 ) { // punto della curva ! p.x =(1.0-u) *punti[i].x + u *punti[i+1].x; p.y =(1.0-u) *punti[i].y + u *punti[i+1].y; } else { // interpola tra due punti dati a // loro volta da 2 pti interpolan.a livello.. p1 = castel( punti, i, NumPun-1, u ); p2 = castel( punti, i+1, NumPun-1, u ); p.x = (1.0-u) * p1.x + u * p2.x; // e questo p.y = (1.0-u) * p1.y + u * p2.y; // e'il punto } // if NumPun return p; } // castel spline vediamo meglio il procedimento dati n punti P k esistono vari tipi di funzioni che passano per i punti dati, ad es. polinomiali, trigonometriche, esponenziali ecc; in grafica sono usate le funzioni interpolanti / approssimanti di tipo polinomiale, in genere sono curve cubiche: y = f(x) (nel piano), qui usiamo la forma parametrica x = fx(u), y = fy(u), z = fz(u) scelta utile se si vogliono rappresentare linee del tipo in figura, dove per alcuni x posso avere piu' valori y... se ho n punti Pk posso esprimere una linea come somma di termini, ciascuno relativo ad un punto Pk : P(u) = somma(per i da 1 a n) Pi * Bi(u) ovvero separatamente: fx(u) = somma(per i da 1 a n) xi * Bi(u) fy(u) = somma(per i da 1 a n) yi * Bi(u) spline dati n punti P k f(x) funzioni polinomiali che passano per/vicino i punti dati: y = f(x), oppure, meglio, in forma parametrica x = fx(u), y = fy(u), (u = parametro che individua un punto della curva) se ho n punti Pk esprimo una linea come somma delle coordinate dei punti pesate con una funzione opportuna B(u): fx(u) = ∑i=1,n xi * Bi(u) fy(u) = ∑i=1,n yi * Bi(u) le Bi sono dette Blending Functions: per ogni u le Bi danno l' influenza del i-esimo punto lungo la curva la Bi esprime "la forza" con cui il punto Pi attrae la la curva; blend -> ORIGIN Middle English : probably of Scandinavian origin and related to Old Norse blanda = to spline per disegnare la curva approssimante i punti Pk dati, x = fx(u), y = fy(u), z = fz(u) se ho n punti Pk posso esprimere una linea come somma di termini, ciascuno relativo ad un punto: fx(u) = somma(per i da 1 a n) xi * Bi(u) fy(u) = somma(per i da 1 a n) yi * Bi(u) le Bi = Blending Functions danno l' influenza del i-esimo punto Pk lungo la curva; ogni Bi esprime "la forza" con cui il punto Pi attrae la la curva; se abbiamo tutte le Bi tali che per qualunque u per j != i risulta Bj (u) == 0 e solo per j==i Bi (u) ==1 allora la curva passera' P1 P2 per tutti i punti Pi P3 spline curva generica data in forma parametrica: fx(u) = somma(per i da 1 a n) xi * Bi(u) fy(u) = somma(per i da 1 a n) yi * Bi(u) Bi = Blending Functions scegliamo le Bi in modo che per j != i vale Bj(u) == 0 e che per j==i vale Bi(u) ==1 (per qualunque u) allora la curva passa per il punto Pi ovvero per i punti di controllo; le funzioni che possono soddisfare a questa richiesta sono tipo: u(u-1)(u-2)...(u-(n-2)) aggiungo fattore tale che Bi (u) ==1, e ho: B1(u) = u(u-1)(u-2)...(u-(n-2)) / (-1)(-2)(-3)..(1-n) che vale 1 per u == -1, poi applico un fattore di scala (denominat.) in modo che: Bi(u) = (u+1)(u)(u-1)..(u-(i-3)) (u-(i-1)) ..(u-(n-2 )) vale 1 per u==i-2 / ( (i-1)(i-2)..(1) (-1) (-2) .. (i-n) ) e vale 0 u==k in questo caso ogni punto controllo partecipa alla curva (come in Casteljau) ... cosa in genere non pratica spline fx(u) = somma(per i da 1 a n) xi * Bi(u) fy(u) = somma(per i da 1 a n) yi * Bi(u) fz(u) = somma(per i da 1 a n) zi * Bi(u) P2 P3 u=0 u=1 P1 P4 u=-1 vediamo un esempio con 4 punti: u=2 scelgo u tale che se u==-1 sono su P1, e ho B1(u)= u(u-1)(u-2) / (-1)(-2)(-3) B1(u) vale 1 se u== -1, e vale 0 se u=0,1,2 se u==0 sono su P2, e ho B2(u)= (u+1)(u-1)(u-2) / (1)(-1)(-2) B2(u) vale 1 se u == 0, e vale 0 se u=-1,1,2 se u==1 sono su P3, e ho B3(u)= (u+1)u(u-2) / (2)(1)(-1) B3(u) vale 1 se u == 1, e vale 0 se u=-1,0,2 se u==2 sono su P4, e ho B4(u)= (u+1)u(u-1) / (3)(2)(-1) B4(u) vale 1 se u == 2 e vale 0 se u=-1,0,1 spline con 4 punti: se u==-1 sono su P1, e ho B1(u)= (u(u-1)(u-2))/((-1)(-2)(-3)) B1(u) vale 1 se u== -1, e vale zero se u=0,1,2 Pj P1 u=-1 P2 P3 u=0 u=1 P4 u=2 se u==0 sono su P2, e ho B2(u)=((u+1)(u-1)(u-2))/((1)(-1)(-2)) x=somma in k di (x *B (u)) k k B2(u) vale 1 se u == 0, e y=somma in k di (yk*Bk(u)) z=somma in k di (zk*Bk(u)) vale zero se u=-1,1,2 se u==1 sono su P3, e ho B3(u)= ((u+1)u(u-2)) /((2)(1)(-1)) B3(u) vale 1 se u == 1, e ... se u==2 sono su P4, e ho B4(u)= ((u+1)u(u-1))/((3)(2)(-1)) B4(u) vale 1 se u == 2 , e ... la curva approssima la curva ideale che interpola i 4 punti al meglio nell'intervallo P2-P3, dove posso ricavare altri punti Pj per u che sta tra 0 e 1, ad es nove punti per valori 0,1/8,2/8,.. fino a 7/8 e 8/8, e quindi ho 8 segmenti nell'intervallo P2-P3 spline con 4 punti: B1(u)= (u(u-1)(u-2))/((-1)(-2)(-3)) B1(u) vale 1 se u== -1 B2(u)=((u+1)(u-1)(u-2))/((1)(-1)(-2)) B2(u) vale 1 se u == 0 B3(u)= ((u+1)u(u-2)) /((2)(1)(-1)) B3(u) vale 1 se u == 1 B4(u)= ((u+1)u(u-1))/((3)(2)(-1)) B4(u) vale 1 se u == 2 lo schema (cubiche) e' applicabile al caso di n punti: per ogni tratto Pk Pk+1 si considerano i 4 punti da Pk-1 a Pk+2 ovvero: nel tratto P2-P3 uso i punti P1-P4 , nel tratto P3-P4 uso i punti P2-P5 , nel tratto P4-P5 uso i punti P3-P6 ecc P2 P3 u=0 u=1 P1 P4 u=-1 u=2 P3 P4 u=0 u=1 P2 P5 u=-1 u=2 P3 u=-1 P4 P5 u=0 u=1 P6 u=2 spline - cubiche approssimanti vediamo in dettaglio lo schema e la realizzazione con procedure ad-hoc di una spline approssimante con funzioni di blending cubiche: blending functions per curva approssimante, somma sempre 1 Bezier approssimanti con 3, 4, 4, 4, 5 punti di controllo una proprieta' delle curve di Bezier approssimanti: all' inizio (e alla fine) curva la tangente e' data dai primi (ultimi) due punti blending functions periodiche (fatte da quadriche) con controllo piu' locale (a segmenti u1,u2,u3..[knots] uniformi) procedure b-spline vediamo una procedura con b-spline cubiche (prog.EGD_4BSPLINE) /* bspline globals [static!] */ const int nb=6; /* 6 line section = knots number */ float b[4][nb]; /*6*4=24 blending values=pesi per xx,yy*/ float xx[100]; /* copia degli nd+5 punti dati */ float yy[100]; /* punto iniziale triplice, quindi +2, punto finale quadruplo, quindi +3 */ bool first_time=true; // la procedura principale e' la bspline: void bspline ( float x[], float y[], int nd, /*dati*/ float LineWidth, float r,float g, float b, float a ); // bspline usa altre due procedure: void setbval( ){ // prepara i coeff. del blending, void make_one ( int k ) // disegna un tratto di curva: vediamo >> procedura b-spline void bspline ( float x[], float y[], int nd, /*dati*/ float myLineWidth, float r,float g, float b, float a ){ if(first_time) /* prepara i coefficienti b[i][j] */ { setbval(); first_time=false; } if(nd<=1) { return; } /* niente da disegnare */ if(nd==2) { ... disegna un segmento e poi ... return; } for( i=0; i< nd; i++ ){ /* fa prima una copia locale */ xx[i+2]=x[i]; yy[i+2]=y[i]; } /* e prepara il punto iniziale triplo, il finale quadr */ xx[0]=x[0]; yy[0]=y[0]; xx[1]=x[0]; yy[1]=y[0]; for(int k=2;k<5;k++) // k=2,3,4 { xx[nd+k]=x[nd-1]; yy[nd+k]=y[nd-1]; } xm=xx[0]; ym=yy[0]; /*used in make_one as previous pt*/ for(i=1; i< nd+2; i++ ) /* finalmente disegna i */ make_one( i ); /* singoli tratti tra i pti dati */ } /*bspline */ procedure b-spline: prepara i coefficienti b setbval prepara i coefficenti b per il calcolo di P(u): (cfr con B0=(1-u)^3, B1=3u(1-u)^2, B2=3u^2(1-u), B3=u^3 void setbval( ){/* prepare blend values for nn lines */ float u, u2, u3, u_1Alla3, b0,b1,b2,b3, Fact_b2; if(first_time) { for (int i=0; i<nb; i++) { /*nb=6=nr segm per sezione*/ u = i/fnb; u2 = u*u; u3 = u2*u; /* u da 0 a 1-1/6 */ u_1Alla3 = (1 - u) * (1 - u) * (1 - u); Fact_b2 = (((1 - u) * u +1) * u +1.0/3.0) / 2.0; b0= u_1Alla3/6.0; // b0= (1.0-u)*(1.0-u)*(1.0-u)/6.0; b2= Fact_b2; // b2= (-3*u3 + 3*u2 +3*u +1)/6.0; b3= u3 / 6.0; // b3= u^3/6.0; b1= 1-b0 -b2 -b3; // b1= (3*u3 - 6*u2 +4 )/6.0; b[0][i]= b0; b[3][i]= b3; b[2][i]= b2; b[1][i]= b1; } /* for i */ first_time=false; } /* if first_time */ } /* setbval */ } /* setbval */ proc fa un tratto di spline void make_one(int k){ /*draw line section xx[k]..xx[k+1] b[0][j],b[1][j]..,b[3][j]=blending weights for xx data, j is the "u parameter", here from 0 to 5(nb=6 knots)*/ int i, j; float xl, yl; if(k<=0) return; glLineWidth(myLineWidth); glColor4f(myr,myg, myb, mya ); /* line strip red */ for (j=0; j<nb; j++){ /* nb=6=linesegment sections nr*/ xl=0; yl=0;/*4 addendums for each point xl,yl,given by points xx[k-1],xx[k],xx[k+1],xx[k+2] */ for (i=0; i<4; i++) { xl=xl+xx[k+i-1] * b[i][ j]; yl=yl+yy[k+i-1] * b[i][ j]; } /* for i */ /* now draw a line */ glBegin(GL_LINE_STRIP); glVertex2f( xm,ym ); glVertex2f( xl,yl ); glEnd(); xm = xl; ym = yl; /*remember current for previous*/ } /* for j */ } /* make */ spline x 3 4 x x x x es di spline con nb = numero segmenti per tratto di linea uguale a 3 (qui sopra) e, a destra, 4(sopra) e 6(sotto) * con 6 segmenti quasi non si nota l'approssimazione della curva con una spezzata 6 spline segue un esempio di codice OpenGL per uso delle procedure previste dalla libreria, che sono la prepara coefficienti : glMap1f( GL_MAP1_VERTEX_3, 0, 1, 3, 5, &(PuntiContr[0][0]) ); // umin=0, umax=1, stride=3, order=6-1 e al posto della make_one abbiamo: glBegin( GL_LINE_STRIP); /*UN tratto di 6 segm*/ for (k=0; k<=nseg; k++) glEvalCoord1f((float)k/nsegf); glEnd(); bsplineOGL(float PTabX[], float PTabY[], int PointCount, float LineW, float r,float g, float b, float a ) { .. kb=0; while( kb < PointCount ) { /* fa PointCount tratti */ /*prepara la matrice coefficenti per disegno seguente:*/ glMap1f( GL_MAP1_VERTEX_3, uMin, uMax, /*0 e 1 */ stride, /* =3, e'4 se coordinate omogenee, altro se uso anche il colore: e' la distanza tra due punti di controllo nell'array PuntiContr */ order, /* =6 qui */ &(PuntiContr[0][0]) ); ...glBegin( GL_LINE_STRIP); /* fa UN tratto di 6 segm*/ for (k=0; k<=nseg; k++) glEvalCoord1f((float)k/nsegf); glEnd(); /* glEval gives the b-spline approxim */ kb = kb+order-1; /*avanza di 5=order-1 posiz */ } /* while kb*/ glDisable( GL_MAP1_VERTEX_3 ); } /* drawOGLSpline */ bsplineOGL(float PTabX[], float PTabY[], int PointCount, float LineW, float r,float g, float b, float a ) { /* prepare Bezier param */ const int PntCountMax=200; ... float PuntiContr[PntCountMax][3]; if(PointCount<=2)return;... kb=0; /*nel while alla fine c'e' kb=kb+order-1 */ while( kb < PointCount ) { /* fa PointCount tratti */ kptLim = order; if( kptLim+kb > PointCount ) kptLim=PointCount-kb; for (kpt=0; kpt<kptLim; kpt++) { PuntiContr[kpt][0] = PTabX[kb+kpt]; /* X */ PuntiContr[kpt][1] = PTabY[kb+kpt]; /* Y */ PuntiContr[kpt][2] = 0.0; /* Z */ } /* for */ glMap1f(GL_MAP1_VERTEX_3,0,1,3,5,&(PuntiContr[0][0])); glEnable( GL_MAP1_VERTEX_3 ); ...glBegin( GL_LINE_STRIP); /* fa UN tratto di 6 segm*/ for (k=0; k<=nseg; k++) glEvalCoord1f((float)k/nsegf); glEnd(); /* glEval gives the b-spline approxim */ kb = kb+order-1; /*avanza di 5=order-1 posiz */ } /* while kb*/ glDisable( GL_MAP1_VERTEX_3 ); } /* drawOGLSpline */ quattro esempi di curve spline (6 punti controllo dati) Spline Cubica, OpenGLspline,Casteljau, Bezier quattro esempi di curve spline (7 punti controllo dati) Spline Cubica, OpenGLspline,Casteljau, Bezier spline nel caso 3D i 4 punti di controllo diventano 16, e le blending-functions diventano somme a due indici, : P(u,v) = somma di Pku,kv*Bku(u)*Bkv(v) in ku e in kv che e' un punto del frammento di superfice definito dai punti di controllo, anche qui, si possono ottenere buone approssimazioni considerando un reticolo di 4x4 punti per disegnare il rettangolino in mezzo; il reticolo si "sposta" lungo il reticolo dato dei punti di controllo, e del reticolo 4x4 interessa ad ogni passo solo la parte centrale, per u e v da 0 a 1 ... fine