#include "MarbleSphere.h"
MarbleSphere::MarbleSphere(void) {}
MarbleSphere::~MarbleSphere(void) {}
void MarbleSphere::CreateSphere(Point3f c,double r,int n)
{
int i,j;
double theta1,theta2,theta3;
Point3f e,p;
if (r < 0)
r = -r;
if (n < 0)
n = -n;
if (n < 4 || r <= 0) {
glBegin(GL_POINTS);
glVertex3f(c.x(),c.y(),c.z());
glEnd();
return;
}
for (j=0;j<n/2;j++) {
theta1 = j * (2*M_PI)/ n -(M_PI/2);
theta2 = (j + 1) * (2*M_PI)/ n -(M_PI/2);
glBegin(GL_QUAD_STRIP);
for (i=0;i<=n;i++) {
theta3 = i * 2*M_PI / n;
e[XX] = cos(theta2) * cos(theta3);
e[YY] = sin(theta2);
e[ZZ]= cos(theta2) * sin(theta3);
p[XX] = c.x() + r * e.x();
p[YY] = c.y() + r * e.y();
p[ZZ] = c.z() + r * e.z();
glNormal3f(e.x(),e.y(),e.z());
glTexCoord2f(i/(double)n,2*(j+1)/(double)n);
glVertex3f(p.x(),p.y(),p.z());
e[XX] = cos(theta1) * cos(theta3);
e[YY] = sin(theta1);
e[ZZ] = cos(theta1) * sin(theta3);
p[XX] = c.x() + r * e.x();
p[YY] = c.y() + r * e.y();
p[ZZ] = c.z() + r * e.z();
glNormal3f(e.x(),e.y(),e.z());
glTexCoord2f(i/(double)n,2*j/(double)n);
glVertex3f(p.x(),p.y(),p.z());
}
glEnd();
}
}
void MarbleSphere::Rotate(float dt)
{
Point3f asse_verticale;
asse_verticale.Zero();
asse_verticale[1] = 1.0;
angolo_rotazione = ((normavel*dt/(attrito))/lung_arco_per_grado);
vettore_rotazione = vet_pos_ris ^ asse_verticale ;
}
void MarbleSphere::setTexture(char *str)
{
ilInit();
iluInit();
ilutRenderer(ILUT_OPENGL);
ti=ilutGLLoadImage(str);
ilutGLBuildMipmaps();
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE );
}
void MarbleSphere::Update(MadMap *mp,float delta_t)
{
if (palla_viva)
{
//=================================aggiungo comp mouse=====================================
vet_velocita=vet_velocita+vet_compon_controllo;
//=========================================================================================
//=======la risultante delle varie velocita non vince l'attrito e mi fermo=================
if(vet_velocita.Norm()<=vet_compon_vel_attrito.Norm())
{
vet_velocita.Zero();return;
}
//=========================================================================================
//======================================= ricavo il triangolo =============================
mp->getTriangle(vet_posizione[0],vet_posizione[2],&triangolo); //ricavo il triangolo...(3 vertici)
//=========================================================================================
//=============ricavo la tile su cui si trova la palla per le caratteristiche fisiche =====
int x=0;
int z=0;
x = floor(vet_posizione.x());
z = floor(vet_posizione.z());
//=========================================================================================
//==============ricavo l'attrito e la gravita della tile su cui si trova la palla==========
vet_compon_vel_gravita.Zero();
if ((x<0)||(z<0)||(z>=MAXMAP)||(x>=MAXMAP))
{ vet_compon_vel_gravita[1]=-160;
attrito=7; }
else {
vet_compon_vel_gravita[1]=(float)mp->Map[x][z].grav; //qui interrogo la mappa con ceil e floor del vet pos per leggere le info di attrito e gravita delle tile
attrito=(float)mp->Map[x][z].attr;}
//===========================================================================================
//=================ricavo la reazione al piano=============================================
float normaG;
float normaR;
normaG = vet_compon_vel_gravita.Norm(); //norma di G
normaR = (normaG * cos(triangolo.pendenza()));
triangolo.normale=triangolo.normale*normaR; //adesso triangolo.normale e' la reazione calcolata
normaR = triangolo.normale.Norm(); //norma reazione vera
//=========================================================================================
//==================================aggiungo comp gravita =================================
vet_velocita=vet_velocita+vet_compon_vel_gravita; //G e' sempre presente
//=========================================================================================
//adesso cerco di capire se la palla tocca o meno la mappa per aggiungere la reazione e l'attrito oppure no
//==============================se tocco la mappa ==================================================================
if (( tocca_mappa_inclinata ) || ( tocca_mappa_piana )) // tocco la mappa aggiungo attr e reaz
{
//aggiungo reazione
vet_velocita=vet_velocita+triangolo.normale;
//calcolo attrito
vet_compon_vel_attrito=-(vet_velocita/(vet_velocita.Norm()/attrito));
//aggiungo attrito
vet_velocita= vet_velocita + vet_compon_vel_attrito ;
//==============================controllo morte=====================
if (vet_velocita[1] < -VELOCITA_DI_MORTE ) palla_viva=false;
muro_di_morte=4;
//===================================================================
//===========================rimbalsi verticali======================
vet_velocita[1]= -vet_velocita[1]/1.5;
if (triangolo.pendenza()!= 0) vet_velocita[1]= -vet_velocita[1]/1.5;//per evitare i salti nei piani inclinati
if( vet_velocita[1]<0) //per evitare le vibrazioni
{
if ( vet_velocita[1] > -800.0) vet_velocita[1]=0;
}
if ( vet_velocita[1]>0)
{
if ( vet_velocita[1] < 800.0) vet_velocita[1]=0;
}
}
//==========================================================================================
//================================calcolo il vet pos risultante=========
normavel=vet_velocita.Norm();
normapos=normavel*delta_t;
vet_pos_ris=vet_velocita*delta_t/200.0;
normavettpos=vet_pos_ris.Norm();
if ((normavettpos >=0.5)&& (( tocca_mappa_inclinata ) || ( tocca_mappa_piana )) ) //il vettore pos massimo in norma e' 1.0
{
vet_pos_ris=vet_pos_ris*(0.5/normavettpos);
normavettpos=vet_pos_ris.Norm();
}
normavettpos=vet_pos_ris.Norm();
//======================================================================
//====================rotazione pallina================================
if (vet_compon_controllo.Norm()>0) Rotate(-delta_t); //rotazione attiva
else Rotate(delta_t);
//=====================================================================
//=========================nuovo vet posizione aggiornato==============
old_vet_pos=vet_posizione; //per il collision verticale
vet_posizione=vet_posizione+vet_pos_ris;
//=======================================================================
//==================================collision detection verticale======================
mp->getTriangle(vet_posizione[0],vet_posizione[2],&triangolo); //ricavo il triangolo...(3 vertici)
float alpha =triangolo.pendenza(); //pendenza del piano
float dislivello ;
float a;
float b;
float c;
float d;
float altezza_punto_centrale;
float altezza_punto_controllo;
float altezza_punto_controllo_obl_1;//punto obliquo di sinistra
float altezza_punto_controllo_obl_2;//punto obliquo di destra
Point3f controllo[4];
for (int i=0;i<4;i++) controllo[i].Zero();
controllo[0][0]=vet_posizione[0]; controllo[0][1]=vet_posizione[1]; controllo[0][2]=vet_posizione[2]-0.5; // nord
controllo[1][0]=vet_posizione[0]+0.5; controllo[1][1]=vet_posizione[1]; controllo[1][2]=vet_posizione[2]; // est
controllo[2][0]=vet_posizione[0]; controllo[2][1]=vet_posizione[1]; controllo[2][2]=vet_posizione[2]+0.5; // sud
controllo[3][0]=vet_posizione[0]-0.5; controllo[3][1]=vet_posizione[1]; controllo[3][2]=vet_posizione[2]; // ovest
//fin qui ho settato le posizioni dei punti di controllo
//===========================================
Point3f controllo_or[4];
for (int i=0;i<4;i++) controllo_or[i].Zero();
controllo_or[0][0]=vet_posizione[0]-0.3535; controllo_or[0][1]=vet_posizione[1]; controllo_or[0][2]=vet_posizione[2]-0.3535; //nord ovest
controllo_or[1][0]=vet_posizione[0]+0.3535; controllo_or[1][1]=vet_posizione[1]; controllo_or[1][2]=vet_posizione[2]-0.3535; //nord est
controllo_or[2][0]=vet_posizione[0]+0.3535; controllo_or[2][1]=vet_posizione[1]; controllo_or[2][2]=vet_posizione[2]+0.3535; //sud est
controllo_or[3][0]=vet_posizione[0]-0.3535; controllo_or[3][1]=vet_posizione[1]; controllo_or[3][2]=vet_posizione[2]+0.3535; //sud ovest
//fin qui ho settato le posizioni dei punti di controllo
//===========================================
altezza_punto_centrale =( mp->getY( vet_posizione.x() , vet_posizione.z() ));//!!!!
for (int i=0;i<4;i++)
{
float differ =(mp->getY(controllo[i][0],controllo[i][2]) -((old_vet_pos[1])-0.5 ));
switch (i)
{
case 0:
altezza_punto_controllo=( mp->getY(controllo[(i)][0],controllo[(i)][2])); //nord
//==============================
altezza_punto_controllo_obl_1=( mp->getY(controllo_or[(i)][0],controllo_or[(i)][2])); //nord ovest
altezza_punto_controllo_obl_2=( mp->getY(controllo_or[(i+1)][0],controllo_or[(i+1)][2])); //nord est
//==============================
if (vet_pos_ris.z()<=0)
{
x = floor(controllo[i][0]);
z = floor(controllo[i][2]);
a= mp->Map[x][z].SW;
b=mp->Map[x][z].SE;
c=mp->Map[x][z+1].NW;
d=mp->Map[x][z+1].NE;
dislivello= altezza_punto_controllo -altezza_punto_centrale;//mi serve nel collision quando la palla sta rimbalzando
if((( ( ((a-c)>0.25)||((b-d)>0.25))&&( dislivello>0 ) &&
(altezza_punto_controllo>(vet_posizione.y()-0.5))) &&
alpha==0) ||(alpha!=0 && differ>0.6)) //obliquio approssimato a 0.6 ,implica che non sale su pendenze con derivata >0.6
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[2]= ceil(vet_posizione[2])-0.5;
vet_velocita[2]= -vet_velocita[2]/1.2; //rimbalso parzialmente anelastico
if ((vet_velocita[2] < -VELOCITA_DI_MORTE )|| (vet_velocita[2] > VELOCITA_DI_MORTE )) palla_viva=false;
muro_di_morte = 0;
}
if (((altezza_punto_controllo_obl_1>(vet_posizione.y())) && //nord ovest
(controllo[i][1]-0.25 ) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
(controllo[3][1]-0.25) >= ( mp->getY(controllo[3][0],controllo[3][2])) &&alpha==0 )
||
((altezza_punto_controllo_obl_1>(vet_posizione.y())) &&
(controllo[i][1]) >= ( mp->getY(controllo[i][0],controllo[i][2])) && //controllo il punto obliquo
(controllo[3][1]) >= ( mp->getY(controllo[3][0],controllo[3][2])) &&alpha!=0 ) )
//ho preso lo spigolo
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[2]= ceil(vet_posizione[2])-0.65;
vet_velocita[2]=-vet_velocita[2]/1.2;
if ((vet_velocita[2] < -VELOCITA_DI_MORTE )|| (vet_velocita[2] > VELOCITA_DI_MORTE )) palla_viva=false;
muro_di_morte = 0;
}
if(((altezza_punto_controllo_obl_2>(vet_posizione.y()))&& //nord est
(controllo[i][1]-0.25) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
(controllo[1][1]-0.25) >= ( mp->getY(controllo[1][0],controllo[1][2])) &&alpha==0 )
||
((altezza_punto_controllo_obl_2>(vet_posizione.y()))&& //controllo il punto obliquo
(controllo[i][1]) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
(controllo[1][1]) >= ( mp->getY(controllo[1][0],controllo[1][2])) &&alpha !=0 ))
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[2]= ceil(vet_posizione[2])-0.65;
vet_velocita[2]=-vet_velocita[2]/1.2;
if ((vet_velocita[2] < -VELOCITA_DI_MORTE )|| (vet_velocita[2] > VELOCITA_DI_MORTE )) palla_viva=false;
muro_di_morte = 0;
}
}
break;
case 1:
altezza_punto_controllo=( mp->getY(controllo[(i)][0],controllo[(i)][2])); //est
//==============================
altezza_punto_controllo_obl_1=( mp->getY(controllo_or[(i)][0],controllo_or[(i)][2])); //nord est
altezza_punto_controllo_obl_2=( mp->getY(controllo_or[(i+1)][0],controllo_or[(i+1)][2])); //sud est
//=====================================
if (vet_pos_ris.x()>=0)
{
x = floor(controllo[i][0]);
z = floor(controllo[i][2]);
a= mp->Map[x][z].NW;
b=mp->Map[x][z].SW;
c=mp->Map[x-1][z].NE;
d=mp->Map[x-1][z].SE;
dislivello= altezza_punto_controllo -altezza_punto_centrale;//mi serve nel collision quando la palla sta rimbalzando
if((( ( ((a-c)>0.25)||((b-d)>0.25))&&( dislivello>0 ) &&
(altezza_punto_controllo>(vet_posizione.y()-0.5))) &&
alpha==0) ||(alpha!=0 && differ>0.6))
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[0]= floor(vet_posizione[0])+0.5;
vet_velocita[0]= -vet_velocita[0]/1.2; //rimbalso parzialmente anelastico
if ((vet_velocita[0] < -VELOCITA_DI_MORTE ) || (vet_velocita[0] > VELOCITA_DI_MORTE ))palla_viva=false;
muro_di_morte = 1;
}
if (((altezza_punto_controllo_obl_1>(vet_posizione.y()))&& //nord est
(controllo[i][1]-0.25) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
(controllo[0][1]-0.25 )>= ( mp->getY(controllo[0][0],controllo[0][2])) && alpha==0 )
||
((altezza_punto_controllo_obl_1>(vet_posizione.y()) )&&
(controllo[i][1]) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
(controllo[0][1]) >= ( mp->getY(controllo[0][0],controllo[0][2])) && alpha!=0 ))
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[0]= ceil(vet_posizione[0])-0.3535;
vet_velocita[0]= -vet_velocita[0]/1.2;
if ((vet_velocita[0] < -VELOCITA_DI_MORTE ) || (vet_velocita[0] > VELOCITA_DI_MORTE ))palla_viva=false;
muro_di_morte = 1;
}
if (((altezza_punto_controllo_obl_2>(vet_posizione.y()))&& //sud est
( controllo[i][1]-0.25) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
( controllo[2][1]-0.25) >= ( mp->getY(controllo[2][0],controllo[2][2]))&& alpha==0 )
||
((altezza_punto_controllo_obl_2>(vet_posizione.y()))&&
( controllo[i][1]) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
( controllo[2][1]) >= ( mp->getY(controllo[2][0],controllo[2][2]))&& alpha!=0 ))
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[0]= ceil(vet_posizione[0])-0.3535;
vet_velocita[0]= -vet_velocita[0]/1.2;
if ((vet_velocita[0] < -VELOCITA_DI_MORTE ) || (vet_velocita[0] > VELOCITA_DI_MORTE ))palla_viva=false;
muro_di_morte = 1;
}
}
break;
case 2:
altezza_punto_controllo=( mp->getY(controllo[(i)][0],controllo[(i)][2])); //sud
//==============================
altezza_punto_controllo_obl_1=( mp->getY(controllo_or[(i)][0],controllo_or[(i)][2])); //sud est
altezza_punto_controllo_obl_2=( mp->getY(controllo_or[(3)][0],controllo_or[(3)][2])); //sud ovest
//=====================================
if (vet_pos_ris.z()>=0)
{
x = floor(controllo[i][0]);
z = floor(controllo[i][2]);
a= mp->Map[x][z].NE;
b=mp->Map[x][z].NW;
c=mp->Map[x][z-1].SE;
d=mp->Map[x][z-1].SW;
dislivello= altezza_punto_controllo -altezza_punto_centrale;//mi serve nel collision quando la palla sta rimbalzando
if((( ( ((a-c)>0.25)||((b-d)>0.25))&&( dislivello>0 ) &&
(altezza_punto_controllo>(vet_posizione.y()-0.5))) &&
alpha==0) ||(alpha!=0 && differ>0.6))
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[2]= floor(vet_posizione[2])+0.5;
vet_velocita[2]= -vet_velocita[2]/1.2; //rimbalso parzialmente anelastico
if ((vet_velocita[2] < -VELOCITA_DI_MORTE )||(vet_velocita[2] > VELOCITA_DI_MORTE )) palla_viva=false;
muro_di_morte = 2;
}
if (((altezza_punto_controllo_obl_1>(vet_posizione.y()))&& //sud est
( controllo[i][1]-0.25) >= ( mp->getY(controllo[(i)][0],controllo[(i)][2])) &&
( controllo[1][1]-0.25) >= ( mp->getY(controllo[(1)][0],controllo[(1)][2])) && alpha==0 )
||
((altezza_punto_controllo_obl_1>(vet_posizione.y()))&&
( controllo[i][1]) >= ( mp->getY(controllo[(i)][0],controllo[(i)][2])) &&
( controllo[1][1]) >= ( mp->getY(controllo[(1)][0],controllo[(1)][2])) && alpha !=0 ))
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[2]= ceil(vet_posizione[2])-0.3535;
vet_velocita[2]=-vet_velocita[2]/1.2;
if ((vet_velocita[2] < -VELOCITA_DI_MORTE )||(vet_velocita[2] > VELOCITA_DI_MORTE )) palla_viva=false;
muro_di_morte = 2;
}
if (((altezza_punto_controllo_obl_2>(vet_posizione.y()))&& //sud ovest
( controllo[i][1]-0.25) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
( controllo[3][1]-0.25) >= ( mp->getY(controllo[3][0],controllo[3][2])) && alpha==0 )
||
((altezza_punto_controllo_obl_2>(vet_posizione.y()))&&
( controllo[i][1]) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
( controllo[3][1]) >= ( mp->getY(controllo[3][0],controllo[3][2])) && alpha!=0 ) )
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[2]= ceil(vet_posizione[2])-0.3535;
vet_velocita[2]=-vet_velocita[2]/1.2;
if ((vet_velocita[2] < -VELOCITA_DI_MORTE )||(vet_velocita[2] > VELOCITA_DI_MORTE )) palla_viva=false;
muro_di_morte = 2;
}
}
break;
case 3:
altezza_punto_controllo=( mp->getY(controllo[(i)][0],controllo[(i)][2])); //ovest
//==============================
altezza_punto_controllo_obl_1=( mp->getY(controllo_or[(i)][0],controllo_or[(i)][2])); //sud ovest
altezza_punto_controllo_obl_2=( mp->getY(controllo_or[(0)][0],controllo_or[(0)][2])); //nord ovest
//=====================================
if (vet_pos_ris.x()<=0)
{
x = floor(controllo[i][0]);
z = floor(controllo[i][2]);
a= mp->Map[x][z].NE;
b=mp->Map[x][z].SE;
c=mp->Map[x+1][z].NW;
d=mp->Map[x+1][z].SW;
dislivello= altezza_punto_controllo -altezza_punto_centrale;//mi serve nel collision quando la palla sta rimbalzando
if((( ( ((a-c)>0.25)||((b-d)>0.25))&&( dislivello>0 ) &&
(altezza_punto_controllo>(vet_posizione.y()-0.5))) &&
alpha==0) ||(alpha!=0 && differ>0.6))
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[0]= ceil(vet_posizione[0])-0.5;
vet_velocita[0]= -vet_velocita[0]/1.2; //rimbalso parzialmente anelastico
if ((vet_velocita[0] < -VELOCITA_DI_MORTE )||(vet_velocita[0] > VELOCITA_DI_MORTE )) palla_viva=false;
muro_di_morte = 3;
}
if (((altezza_punto_controllo_obl_1>(vet_posizione.y()))&& //sud ovest
(controllo[i][1]-0.25) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
(controllo[2][1]-0.25) >= ( mp->getY(controllo[2][0],controllo[2][2])) && alpha==0)
||
((altezza_punto_controllo_obl_1>(vet_posizione.y()))&&
(controllo[i][1]) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
(controllo[2][1]) >= ( mp->getY(controllo[2][0],controllo[2][2]))&& alpha!=0))
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[0]= ceil(vet_posizione[0])-0.65;
vet_velocita[0]= -vet_velocita[0]/1.2;
if ((vet_velocita[0] < -VELOCITA_DI_MORTE )||(vet_velocita[0] > VELOCITA_DI_MORTE )) palla_viva=false;
muro_di_morte = 3;
}
if ( ((altezza_punto_controllo_obl_2>(vet_posizione.y()))&& //nord ovest
(controllo[i][1]-0.25) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
(controllo[0][1]-0.25) >= ( mp->getY(controllo[0][0],controllo[0][2]))&& alpha==0)
||
((altezza_punto_controllo_obl_2>(vet_posizione.y()))&&
(controllo[i][1]) >= ( mp->getY(controllo[i][0],controllo[i][2])) &&
(controllo[0][1]) >= ( mp->getY(controllo[0][0],controllo[0][2]))&& alpha!=0))
{
vet_posizione[1]= old_vet_pos[1];
vet_posizione[0]= ceil(vet_posizione[0])-0.65;
vet_velocita[0]=-vet_velocita[0]/1.2;
if ((vet_velocita[2] < -VELOCITA_DI_MORTE )|| (vet_velocita[2] > VELOCITA_DI_MORTE )) palla_viva=false;
muro_di_morte = 3;
}
}
break;
}
}
//============================================collision det inclinato====================================
for (int i=0;i<4;i++) controllo[i].Zero();
controllo[0][0]=vet_posizione[0]; controllo[0][1]=vet_posizione[1]; controllo[0][2]=vet_posizione[2]-0.5;
controllo[1][0]=vet_posizione[0]+0.5; controllo[1][1]=vet_posizione[1]; controllo[1][2]=vet_posizione[2];
controllo[2][0]=vet_posizione[0]; controllo[2][1]=vet_posizione[1]; controllo[2][2]=vet_posizione[2]+0.5;
controllo[3][0]=vet_posizione[0]-0.5; controllo[3][1]=vet_posizione[1]; controllo[3][2]=vet_posizione[2];
//fin qui ho settato le posizioni dei punti di controllo
for (int i=0;i<4;i++)
{
float differ =( (vet_posizione[1]) -mp->getY(controllo[i][0],controllo[i][2]) );
if (differ <= 0.45) tocca_mappa_inclinata=true;
else tocca_mappa_inclinata=false;
}
//=======================================================================================================
//==================================collision detection orizzontale======================================
if (vet_posizione[1] <= mp->getY(vet_posizione[0],vet_posizione[2])+0.5) //collision detection
{
vet_posizione[1]= mp->getY(vet_posizione[0],vet_posizione[2])+0.5 ;
tocca_mappa_piana=true;
}
else tocca_mappa_piana=false;
//=======================================================================================================
//==============================================================================================================
vet_compon_controllo.Zero();
vet_compon_vel_gravita.Zero();
triangolo.normale.Zero();
}
else vet_posizione=old_vet_pos;
}