Bezier
Marte Horne / Student 2002
Å tegne:>Tennis

Tenniskamp

Hva
Demonstrasjon av bezier og animasjon i en tenniskamp

skisse

I denne modulen er det visse temaer innen Open GL jeg vil konsentrere meg om. Det første jeg vil konsentrere meg om er alle komponentene som er med i spillet. Denne modulen er veldig grunnleggende når det gjelder disse komponentene. Jeg vil prøve å forklare dette utover i modulen.
Når komponentene er forklart vil jeg så forklare og greie ut om hvordan Josefine og Josef er i stand til å spille mot hverandre.

Temaer i modulen:

  • Grunnleggende GL-komponenter
  • Bezierflater
  • Utvikle personene
  • Synkronisere bevegelsene i armen når man skal slå en ball
  • Vite når ballen treffer racketen

Illustrasjon

Den viktigste bevegelsen i spillet er banen til ballen. Under ser du noen screen-shoots av hvordan to av de fire banene går. Dette er punkter som ligger i en array, og som er tegnet opp som en Bezier-kurve.

skisse_2
Josefine server

skisse_3
Josef slår tilbake

Komponentene

Komponentene jeg har brukt i dette prosjektet er bygget opp av en del av de vanlige grafiske delene som OpenGL og gl4java har i sitt bibliotek.
Som:

  • Kule:
    glu.gluSphere( qd , 0.4f , 30 , 30 );
    	            
  • Sylinder:
    glu.gluCylinder( qd , 0.1 , 0.1 , 0.7 , 20 , 20 );
    	            
  • Disk:
    glu.gluDisk( qd , 0.1f , 0.4f , 20 , 20 );
                   
  • Polygon:
    gl.glBegin(GL_POLYGON);
        gl.glVertex3f(-0.5f, -0.0f,  0.5f);
        gl.glVertex3f( 0.5f, -0.0f,  0.5f);
        gl.glVertex3f( 0.5f, -0.0f, -0.5f);
        gl.glVertex3f(-0.5f, -0.0f, -0.5f);
    gl.glEnd();
                   
  • Bezier:
    float ctrlpoints[] =
    {
        -0.5f, 0.0f,  0.5f,
         0.5f, 0.0f,  0.5f,
    
        -0.5f, 0.0f, -0.5f,
         0.5f, 0.0f, -0.5f
    };
    
    gl.glMap2f(GL_MAP2_VERTEX_3,0.0f,1.0f,3,2,0.0f,1.0f,6,2,ctrlpoints);
    gl.glEnable(GL_MAP2_VERTEX_3);
    gl.glMapGrid2f(10, 0.0f, 1.0f, 10, 0.0f, 1.0f);
    gl.glEnable(GL_DEPTH_TEST);
    gl.glShadeModel(GL_FLAT);
    gl.glEvalMesh2(GL_LINE, 0, 10, 0, 10);
                   
  • Resultat:

    komp_1

Som sagt så vil jeg gå ganske tett inn på hver enkelt komponent som er laget til kampen. Under følger en oversikt over dette.

[Josef, Josefine og dommeren] [Nettet] [Banen] [Racketen] [Skjørtet] [Stolen]

  • Josef, Josefine og dommeren

    Josefine.java
    Josef.java
    Referee.java

    Hovedkomponentene i spillet er Josef og Josefine. Jeg har tatt utgangspunkt i den originale Josef, på sidene til Børre. Dommeren er også bygget opp etter dette prinsippet.
    Personene er bygget opp med sylindere og kuler, hvor du kan rotere rundt alle vinkler i alle kuleleddene. I alle de tre person-klassene, har jeg en get-metode og en set-metode for hvert ledd. Jeg bruker på langt nær alle disse metodene, men hvis jeg senere vil utvikle bevegelsene, er det greit å ha. Alle vinklene i hvert ledd ligger i to-dimensjonale arrayer.

    utvikling_1

    For eksempel:

       private float v_shoulder_left[] = {0.0f, 0.0f, 0.0f};
       private float v_overarm_left[] = {0.0f, 0.0f, 0.0f};
       private float v_underarm_left[] = {0.0f, 0.0f, 0.0f};
       private float v_hand_left[] = {0.0f, 0.0f, 0.0f};
    				
    Så når jeg skal rotere på vinkler i armene bruker jeg bare set-metodene som ligger i de Josef.java, Josefine.java eller Referee.java.
       public void setv_overarm_right(float v[])
       {
          v_overarm_right = v;
       }
    
       josef.setv_overarm_right(josef_overarm_right);
    				
    Måten personene er bygget opp på er for eksempel slik: (Her bygges det venstre benet til Josefine)
       //left leg
       gl.glPushMatrix();
          gl.glRotatef(josefine.getv_hip_left()[1],0,1,0);
          gl.glRotatef(josefine.getv_hip_left()[0],1,0,0);
          gl.glRotatef(josefine.getv_hip_left()[2],0,0,1);
          glu.gluCylinder(qd,0.045*josefine.getsize(),
          0.045*josefine.getsize(), 0.2*josefine.getsize(),20,20);
          gl.glTranslated(0.0f,0.0f,0.2f*josefine.getsize());
          glu.gluSphere(qd,0.07f*josefine.getsize(),20,20);
          gl.glRotatef(josefine.getv_thigh_left()[0],1,0,0);
          gl.glRotatef(josefine.getv_thigh_left()[1],0,1,0);
          gl.glRotatef(josefine.getv_thigh_left()[2],0,0,1);
          glu.gluCylinder(qd,0.045*josefine.getsize(),
          0.045*josefine.getsize(), 0.5*josefine.getsize(),20,20);
          gl.glTranslated(0.0f,0.0f,0.5f*josefine.getsize());
          glu.gluSphere(qd,0.07f*josefine.getsize(),20,20);
          gl.glRotatef(josefine.getv_leg_left()[0],1,0,0);
          gl.glRotatef(josefine.getv_leg_left()[1],0,1,0);
          gl.glRotatef(josefine.getv_leg_left()[2],0,0,1);
          glu.gluCylinder(qd,0.045*josefine.getsize(),
          0.045*josefine.getsize(), 0.45*josefine.getsize(),20,20);
          gl.glTranslated(0.0f,0.0f,0.45f*josefine.getsize());
          glu.gluSphere(qd,0.07f*josefine.getsize(),20,20);
          gl.glRotatef(josefine.getv_foot_left()[0],1,0,0);
          gl.glRotatef(josefine.getv_foot_left()[1],0,1,0);
          gl.glRotatef(josefine.getv_foot_left()[2],0,0,1);
          glu.gluCylinder(qd,0.065*josefine.getsize(),
          0.0*josefine.getsize(), 0.3*josefine.getsize(),20,20);
       gl.glPopMatrix();
    				


  • Nettet



    Nettet.java

    Et nett må til på en tennisbane :)
    Dette er to cylindere med en bezierflate mellom. Der de gulene punktene er tegnet ligger alle kontrollpunktene. Når ballen etterhvert treffer nettet, vil noen av kontrollpunktene forandre seg, slik at nettet går bakover. (Mer under spillet).

    utvikling_2

    Her er kontrollpunktene jeg har brukt:

       private float ctrlpoints[] =
       {
          -2.5f,  0.0f, 0.0f,	-2.0f,  0.0f, 0.0f,
          -1.5f,  0.0f, 0.0f, 	-1.0f,  0.0f, 0.0f,
          -0.5f,  0.0f, 0.0f,	 0.0f,  0.0f, 0.0f,
           0.5f,  0.0f, 0.0f,	 1.0f,  0.0f, 0.0f,
           1.5f,  0.0f, 0.0f,	 2.0f,  0.0f, 0.0f,
           2.5f,  0.0f, 0.0f,
    
          -2.5f,  0.3f, 0.0f,	-2.0f,  0.3f, 0.0f,
          -1.5f,  0.3f, 0.0f, 	-1.0f,  0.3f, 0.0f,
          -0.5f,  0.3f, 0.0f,	 0.0f,  0.3f, 0.0f,
           0.5f,  0.3f, 0.0f,	 1.0f,  0.3f, 0.0f,
           1.5f,  0.3f, 0.0f,	 2.0f,  0.3f, 0.0f,
           2.5f,  0.3f, 0.0f,
    
          -2.5f,  0.6f, 0.0f,	-2.0f,  0.6f, 0.0f,
          -1.5f,  0.6f, 0.0f, 	-1.0f,  0.6f, 0.0f,
          -0.5f,  0.6f, 0.0f,	 0.0f,  0.6f, 0.0f,
           0.5f,  0.6f, 0.0f,	 1.0f,  0.6f, 0.0f,
           1.5f,  0.6f, 0.0f,	 2.0f,  0.6f, 0.0f,
           2.5f,  0.6f, 0.0f,
    
          -2.5f,  0.9f, 0.0f,	-2.0f,  0.9f, 0.0f,
          -1.5f,  0.9f, 0.0f, 	-1.0f,  0.9f, 0.0f,
          -0.5f,  0.9f, 0.0f,	 0.0f,  0.9f, 0.0f,
           0.5f,  0.9f, 0.0f,	 1.0f,  0.9f, 0.0f,
           1.5f,  0.9f, 0.0f,	 2.0f,  0.9f, 0.0f,
           2.5f,  0.9f, 0.0f,
       };
    			


    Og her er måten jeg har tegnet det på. I gl.glEvalMesh2, har jeg brukt LINE, slik at det blir som et nett. Hvis jeg istede her hadde brukt, FILL, ville det bli en fyllt flate.

       //fargen blir svart
       setNormalMaterial_black();
    
       //rotere 90 grader rundt y-aksen
       gl.glRotatef(90, 0.0f, 1.0f, 0.0f);
    
       //maper opp alle punktene til nettet
       gl.glMap2f(GL_MAP2_VERTEX_3,0.0f,1.0f,3,11,0.0f,1.0f,33,4,ctrlpoints);
       gl.glEnable(GL_MAP2_VERTEX_3);
    
       //Maper opp rutenettet med 10*30 ruter
       gl.glMapGrid2f(30, 0.0f, 1.0f, 10, 0.0f, 1.0f);
       gl.glEnable(GL_DEPTH_TEST);
       gl.glShadeModel(GL_FLAT);
       gl.glEvalMesh2(GL_LINE, 0, 30, 0, 10);
    	      


  • Banen


    Dette er banen. Den er bygget opp av mange små hvite polygoner som er lagt utover et stort grønnt polygon. Jeg kunne ha gjort dette annerledes, ved foreksempel å legge på en textur, men dette ble ikke så pent som det er nå.

    utvikling_6

    Eksempel på et polygon:
       //the green ground around
       gl.glBegin(GL_POLYGON);
          setNormalMaterial_green();
          gl.glVertex3f(-6.0f, -0.01f,  4.0f);
          gl.glVertex3f( 6.0f, -0.01f,  4.0f);
          gl.glVertex3f( 6.0f, -0.01f, -4.0f);
          gl.glVertex3f(-6.0f, -0.01f, -4.0f);
       gl.glEnd();
             
  • Racketen



    Racket.java

    Racketen har jeg bygget opp av en bezierflate og en sylinder.

    utvikling_7

    Denne er altså bygget opp av en Bezierflate som er bygget opp på følgende måte:

       private float RACKET = 0.5f;
       private int UN =5;
       private int VN =7;
    
       float ctr1=0.3f*RACKET;
       float ctr2=0.7f*RACKET;
       float ctr3=0.4f*RACKET;
       float ctr4=0.15f*RACKET;
       float ctr5=0.1f*RACKET;
       float ctr6=0.05f*RACKET;
    
       float ctrz1=0.0f*RACKET;
       float ctrz2=0.3f*RACKET;
       float ctrz3=1.0f*RACKET;
       float ctrz4=0.85f*RACKET;
       float ctrz5=0.9f*RACKET;
       float ctrz6=RACKET*1.2f;
    			

       float M[] =
       {
          0.0f, 0.0f, 0.0f,     0.0f, 0.0f, 0.0f,
          0.0f, 0.0f, 0.0f,     0.0f, 0.0f, 0.0f,
          0.0f, 0.0f, 0.0f,
    
           ctr1, ctrz1, 0.0f,   0.0f, ctrz1, 0.0f,
           0.0f, ctrz1, 0.0f,   0.0f, ctrz1, 0.0f,
          -ctr1, ctrz1, 0.0f,
    
           ctr2, ctrz2, 0.0f,   0.0f, ctrz2, 0.0f,
           0.0f, ctrz2, 0.0f,   0.0f, ctrz2, 0.0f,
          -ctr2, ctrz2, 0.0f,
    
           ctr3, ctrz3, 0.0f,    ctr3, ctrz3, 0.0f,
           0.0f, ctrz3, 0.0f,   -ctr3, ctrz3, 0.0f,
          -ctr3, ctrz3, 0.0f,
    
           ctr4, ctrz4, 0.0f,    ctr4, ctrz4, 0.0f,
           0.0f, ctrz4, 0.0f,   -ctr4, ctrz4, 0.0f,
          -ctr4, ctrz4, 0.0f,
    
           ctr5, ctrz5, 0.0f,    ctr5, ctrz5, 0.0f,
           0.0f, ctrz5, 0.0f,   -ctr5, ctrz5, 0.0f,
          -ctr5, ctrz5, 0.0f,
    
           ctr6, ctrz6, 0.0f,   0.0f, ctrz6, 0.0f,
           0.0f, ctrz6, 0.0f,   0.0f, ctrz6, 0.0f,
          -ctr6, ctrz6, 0.0f,
       };
       		
  • Skjørtet


    Som en hver kvinnelig tennisspiller, må Josefine få på seg et skjørt. Dette er bygd opp at to like bezierflater, hvor den ene er rotert 180 grader. Her har jeg tatt utgangspunkt i Påskegg eksempelet til Børre.

    josefine_skjort_2 josefine_skjort_3 josefine_skjort_4 josefine_skjort

    Og her følger hvordan det er bygd opp:

       //The skirt
       private int UN =5;
       private int VN =4;
    
       //size
       private float SKIRT = 0.4f;
    
       float ctr0=0.1f*SKIRT;
       float ctr1=0.5f*SKIRT;
       float ctr2=0.7f*SKIRT;
       float ctr3=0.7f*SKIRT;
       /** offset along z */
       float ctz1=0.0f*SKIRT;
       float ctz2=0.3f*SKIRT;
       float ctz3=1.0f*SKIRT;
       float ctz4=SKIRT;
    
       /** factor for circlecompensation */
       float rf=1.35f;
       /** Bezier points */
       float M[] =
       {
             ctr0,0.0f,ctz1,
             ctr0,ctr0,ctz1,
             0.0f,rf*ctr0,ctz1,
             -ctr0,ctr0,ctz1,
             -ctr0,0.0f,ctz1,
    
             ctr1,0.0f,ctz1,
             ctr1,ctr1,ctz1,
             0.0f,rf*ctr1,ctz1,
             -ctr1,ctr1,ctz1,
             -ctr1,0.0f,ctz1,
    
             ctr2,0.0f,ctz2,
             ctr2,ctr2,ctz2,
             0.0f,rf*ctr2,ctz2,
             -ctr2,ctr2,ctz2,
             -ctr2,0.0f,ctz2,
    
             ctr3,0.0f,ctz3,
             ctr3,ctr3,ctz3,
             0.0f,rf*ctr3,ctz3,
             -ctr3,ctr3,ctz3,
             -ctr3,0.0f,ctz3
       };
    
       //***************************************************//
    
       gl.glMap2f(GL_MAP2_VERTEX_3,0.0f,1.0f,3,josefine.getUN(),
                  0.0f,1.0f,3*josefine.getUN(),josefine.getVN(),
                  josefine.getM());
    
       gl.glEnable(GL_MAP2_VERTEX_3);
       gl.glEnable(GL_AUTO_NORMAL);
       gl.glEnable(GL_NORMALIZE);
    
       gl.glMapGrid2f(20,0.0f,1.0f,20,0.0f, 1.0f);
       gl.glFrontFace(GL_CW);
       gl.glEvalMesh2(GL_FILL, 0, 20, 0, 20);
       gl.glFrontFace(GL_CCW);
    
       gl.glRotatef(180.0f,0.0f,0.0f,1.0f);
       gl.glEvalMesh2(GL_FILL, 0, 20, 0, 20);
       gl.glFrontFace(GL_CCW);
    	      
  • Stolen


    Her er dommerstolen som dommeren sitter på. Den er bygget opp av sylindere og polygoner.

    utvikling_14

Spillet



Spillet er lagt opp slik at jeg har en fast bestemt bane hvor ballen skal gå. Denne banen ligger i en array med koordinater. Under ser du hvordan de fire forskjellige banenen går. Banene er tegnet opp ved hjelp av bezier-kurver.

utvikling_10
Josefine server

utvikling_11
Josef slår tilbake

utvikling_12
Josefine slår tilbake igjen

utvikling_13
Josef slår ballen i nettet og taper

Jeg vet alltid i hvilket punkt racketen og ballen møter hverandre. Så da blir det enkelt å putte dette sammen å synkronisere bevegelsene. Ballen vet jeg hvor er ved å se på arrayen med alle koordinatene til hvor ballen skal gå. Et eksempel er når Josef skal slå tilbake ballen fra Josefine sin serve:

   //Ballen blir slått av Josef, ned i bakken
   for (int i = 0; i < (playing.getUN_3()*3)-2; i++)
   {
      //Josef roterer armen sin
      josef_overarm_right[0]  = teller_1;
      josef.setv_overarm_right(josef_overarm_right);

      //dommeren vrir på hodet...
      referee_neck[2]  = teller_2;
      referee.setv_neck(referee_neck);

      //ballen går videre
      ball = i;

      //oppdater framen
      display();

      i = i + 2;
      teller_1+=2;
      teller_2 = teller_2 + 4.5f;
   }
		


gl.glPushMatrix();
   if (kast)
   {
      gl.glTranslated(playing.getkastPoints()[ball],
      playing.getkastPoints()[ball+1],
      playing.getkastPoints()[ball+2]);
   }
   else if (hit_1)
   {
      gl.glTranslated(playing.getctrlpoints_1()[ball],
      playing.getctrlpoints_1()[ball+1],
      playing.getctrlpoints_1()[ball+2]);
   }
   else if (sprett_1)
   {
      gl.glTranslated(playing.getctrlpoints_2()[ball],
      playing.getctrlpoints_2()[ball+1],
      playing.getctrlpoints_2()[ball+2]);
   }
   else if (hit_2)
   {
      gl.glTranslated(playing.getctrlpoints_3()[ball],
      playing.getctrlpoints_3()[ball+1],
      playing.getctrlpoints_3()[ball+2]);
   }
   .
   .
   .
   .
   else
   {
      gl.glTranslated(playing.getctrlpoints_6()[(playing.getUN_6()*3)-3],
      playing.getctrlpoints_6()[(playing.getUN_6()*3)-2],
      playing.getctrlpoints_6()[(playing.getUN_6()*3)-1]);
   }

   //the ball
   glu.gluSphere(qd,0.07f,30,30);
gl.glPopMatrix();
	   


utvikling_15 utvikling_16

   //The net changes when the ball hits
   if (i == 36)
   {
      nettet.ctrlpoints[116] = 0.0f;
      nettet.ctrlpoints[83] = 5.0f;
   }
   else if (i == 39)
   {
      nettet.ctrlpoints[83] = 0.0f;
      nettet.ctrlpoints[50] = 5.0f;
   }
      else if (i == 42)
   {
      nettet.ctrlpoints[83] = 0.0f;
      nettet.ctrlpoints[50] = 5.0f;
   }
   else if (i == 45)
   {
      nettet.ctrlpoints[50] = 0.0f;
      nettet.ctrlpoints[17] = 5.0f;
      finish = true;
   }
	   

Videreutvikling

Det er selvfølgelig mange ting man kunne gjort annerledes, bedre og mer effektivt. Men siden dette bare er et 4-vekttallskurs har jeg valgt å begrense meg til det jeg syntes jeg hadde tid og kapasitet til.

Det minst krevende å forandre på er nok selve layouten til alle komponentene. Jeg kunne nok ha gjort mer utseendemessig, men nå var det ikke det jeg mest skulle konsentrere meg om, så det er noe som kan komme en senere anledning. Målet med komponentene var å lage det så grunnleggende som mulig, så en nybegynner, greit kan lære seg dette.

Når det gjelder hvordan selve spillet er lagt opp, er det nok dette jeg ville utviklet videre på hvis dette hadde vært et større kurs. Nå er spillet bygd opp på den måten at jeg bestemmer hvor ballen skal gå og hvor den skal ende opp. Det som hadde vært interessant hadde vært og utviklet et spill hvor ballen ble bestemt ut i fra hvor den traff på racketen, hvilken vinkel ballens bane hadde og hvor på banen ballen lander. Da måtte Josef og Josefine beveget seg ut fra hvor ballen kom. Kanskje også ta et skritt til høyre eller venstre for å få tak i ballen.

Referanser
  1. The OpenGL Programming Guide, 6 editionDave Schreiner, Mason Woo,Jackie Neider,Tom Davies2007Addison-Wesley Professional0321481003www.opengl.org/documentation/red_book/14-03-2010
  1. Computer Graphics Using OpenGL, 3nd editionF.S. Hill, S.M Kelly2001Prentice Hall0131496700
[1] [2]

Klassene

MyGLCanvas.java Dette er hovedklassen hvor mye av GL-kode blir kjørt.
Tennis.java Denne klassen startet opp hele programmet. Herfra kalles MyGLCanvas.java.
Playing.java Her ligger alle arrayene med koordinatene til hvor ballen skal gå. Også en del get-metoder for å hente ut verdiene.
ColorsAndComponents.java I denne klassen ligger alle komponentene i programmet som ikke skal bevege på seg. Feks banen og stolen til dommeren. Her ligger også alle fargene jeg bruker. Dette er en GL-klasse.
Nettet.java Her ligger kontrollpunktene til tennis-nettet og et par get-metoder.
Josefine.java Her er alle vinklene til Josefines kropp. Hodet, kroppen, armer og ben. Også mange set- og get-metoder for å forandre på bevegelser fra klassen MyGLCanvas.java.
Josef.java Her er alle vinklene til Josefs kropp. Hodet, kroppen, armer og ben. Også mange set- og get-metoder for å forandre på bevegelser fra klassen MyGLCanvas.java.
Referee.java Her er alle vinklene til dommerens kropp. Hodet, kroppen, armer og ben. Også mange set- og get-metoder for å forandre på bevegelser fra klassen MyGLCanvas.java.
Racket.java Her ligger kontrollpunktene til racketen og et par get-metoder.
Game.java Splash-screen

Alle filene: alt.zip.

Javadoc kan du finne her

Utviklet i GL4Java på Windows 2000 plattform

Har aldri spilt tennis før :)

Vedlikehold
Skrevet mai 2002, Marte Horne
Redaksjonelle endringer Børre Stenseth, juni 2002
(Velkommen) Å tegne:>Tennis (Monster)