Fotball
Geometri
"Buckyball" er et kjent fenomen både i matematikken, naturen og på fotballbanen. Formen på en buckyball er beskrevet ved hjelp av det gyldne snitt (1.61803399...). Dette forholdstallet kan beregnes som kvotienten mellom to nabotall i Fibonacci-rekka og det inngår i mange resonnementer i matematikken og det finnes i naturlige former. Det tillegges også en estetisk betydning som et "naturlig" eller balansert forhold, f.eks. som forholdet mellom høyde og bredde i et rektangel. Det ser ut til at det gyldne snitt i en del sammenhenger beskriver fenomener som er forbundet med maksimal pakking av former.
Buckyball er et kjent begrep for kjemikere og beskriver en ordning av 60 carbonatomer i hjørnene til figuren. Figuren er oppkalt etter R. Buckminster Fuller.
En buckball har 60 hjørner, 20 6-kanter og 12 5-kanter. Figuren er fra GoldenNumber.net [1] .
Dataene som er brukt til å tegne buckyball i denne modulen ser du her, i C#-syntax:datadef.html
Når vi skal tegne en slik figur med flater, lys og egenskygge, er det viktig at vi ordner punktene i et polygon konsekvent. Det vil si at vi må beskrive alle polygoner enten med eller mot klokka. For at dette skal ha mening må vi definere et ståsted. En fruktbar modell er å si at vi står foran fronten på polygonet og nevner hjørnene mot klokka (OpenGL default). Vi kan omdefinere hva som er foran og bak ved følgende OpenGL-kommandoer:
glFrontFace(GL_CW) glFrontFace(GL_CCW)
Der vi kan sette retningen med klokka (CW) eller mot klokka (CCW), sett mot fronten.
Når vi har fått orden på dette, kan vi regne ut alle normalene vi trenger ved å ta kryssproduktet av to vektorer i flaten. For våre data kan det gjøres slik, bare vist for polygonene med 6 kanter.
static void PrepareData() { // calculate normals for pol6:normal6 and pol5:normal5 // Cross product is used:c=axb=(a2b3-a3b2, a3b1-a1b3, a1b2-a2b1) // The normals are normalized (length=1) for (int ix = 0; ix < 20; ix++) { float ax = pol6[ix, 2, 0] - pol6[ix, 0, 0]; float ay = pol6[ix, 2, 1] - pol6[ix, 0, 1]; float az = pol6[ix, 2, 2] - pol6[ix, 0, 2]; float bx = pol6[ix, 3, 0] - pol6[ix, 0, 0]; float by = pol6[ix, 3, 1] - pol6[ix, 0, 1]; float bz = pol6[ix, 3, 2] - pol6[ix, 0, 2]; float x = ay * bz - az * by; float y = az * bx - ax * bz; float z = ax * by - ay * bx; float l=(float)Math.Sqrt(x*x+y*y+z*z); normal6[ix,0]=x/l; normal6[ix,1]=y/l; normal6[ix,2]=z/l; } ... }
Selve uttegningen av polygoner er rett fram:
static void DrawBuckyBall() { int mode = Gl.GL_POLYGON; // polygons with 6 edges SetWhiteMaterial(); for (int p = 0; p < 20; p++) { Gl.glBegin(mode); Gl.glNormal3f(normal6[p, 0], normal6[p, 1], normal6[p, 2]); for (int ix = 0; ix < 6; ix++) Gl.glVertex3f(pol6[p, ix, 0], pol6[p, ix, 1], pol6[p, ix, 2]); Gl.glEnd(); } // polygons with 5 edges SetBlackMaterial(); for (int p = 0; p < 12; p++) { Gl.glBegin(mode); Gl.glNormal3f(normal5[p, 0], normal5[p, 1], normal5[p, 2]); for (int ix = 0; ix < 5; ix++) Gl.glVertex3f(pol5[p, ix, 0], pol5[p, ix, 1], pol5[p, ix, 2]); Gl.glEnd(); } }
Textur
Vi ønsker å legge en tekstur på alle 6-kantene. Vi tar utgangspunkt i en kvadratisk tekstur og vil finne hvilke koordinater i teksturen vi vil mappe til 6-kantenes hjørner.
Ut fra figuren ser vi at:
p0 | d/3 , 0 |
p1 | 2d/3 , 0 |
p2 | d-(d/3)cos(60) , (d/3)sin(60) |
p3 | d-(2d/3)cos(60) , (2d/3)sin(60) |
p4 | (2d/3)cos(60) , (2d/3)sin(60) |
p5 | (d/3)cos(60) , (d/3)sin(60) |
der d er siden i trekanten og kvadratet. Når vi oppgir koordinater i en bitmap, som i algoritmen nedenfor, har d alltid verdien 1, uavhengig av dimensjonene på bitmapen i pixler. En bitmap har logisk utstrekning fra (0,0) til (1,1).
Siden OpenGL bare vil ha teksturer som
har formatet: 2n*2m, må vi forsyne programmet med
bitmapper der vi har plassert den figuren vi vil vise slik at den faller innenfor den trekanten
vi har antydet på figuren ovenfor. Dersom vi (av mangel på fantasi)
vil tegne dette bildet som er 256 x 256 pixler på hver av 6-kantene,
så må vi plassere det vi vil vise fram i følge resonnementet over.
Dersom vi ikke vil ha en forvrengning av bildet.
Teksturmappingen for 6-kantene settes opp slik i arrayen tex6:
// calculate mapping coordinates for texture float d=1.0f; float cos60=(float)Math.Cos(Math.PI/3.0);//60 degrees float sin60=(float)Math.Sin(Math.PI/3.0); tex6[0,0]=d/3.0f; tex6[0,1]=0.0f; tex6[1,0]=2.0f*d/3.0f; tex6[1,1]=0.0f; tex6[2,0]=d-(d/3.0f)*cos60; tex6[2,1]=(d/3.0f)*sin60; tex6[3,0]=d-(2.0f*d/3.0f)*cos60; tex6[3,1]=(2.0f*d/3.0f)*sin60; tex6[4,0]=(2.0f*d/3.0f)*cos60; tex6[4,1]=(2.0f*d/3.0f)*sin60; tex6[5,0]=(d/3.0f)*cos60; tex6[5,1]=(d/3.0f)*sin60;
Uttegningen blir slik:
static void DrawBuckyBall() { int mode = Gl.GL_POLYGON; // polygons with 6 edges SetWhiteMaterial(); for (int p = 0; p < 20; p++) { Gl.glBegin(mode); Gl.glNormal3f(normal6[p, 0], normal6[p, 1], normal6[p, 2]); for (int ix = 0; ix < 6; ix++) { Gl.glTexCoord2f(tex6[ix, 0], tex6[ix, 1]); Gl.glVertex3f(pol6[p, ix, 0], pol6[p, ix, 1], pol6[p, ix, 2]); } Gl.glEnd(); } // polygons with 5 edges, no texture SetBlackMaterial(); for (int p = 0; p < 12; p++) { Gl.glBegin(mode); Gl.glNormal3f(normal5[p, 0], normal5[p, 1], normal5[p, 2]); for (int ix = 0; ix < 5; ix++) Gl.glVertex3f(pol5[p, ix, 0], pol5[p, ix, 1], pol5[p, ix, 2]); Gl.glEnd(); } }
Innlastingen og prepareringen av bitmaps er f.eks. slik:
// holding a texture, need a reference variable private static int[] texture = new int[1];
static void LoadTexture() { // get a bitmap //string filename = Application.StartupPath + "\\Marrakech.jpg"; //string filename = Application.StartupPath + "\\chesspiece.gif"; string filename = Application.StartupPath + "\\ronaldinho.jpg"; //string filename = Application.StartupPath + "\\cloth.bmp"; Bitmap bm = new Bitmap(filename); // set up texture if (bm != null) { // get a place to store texture Gl.glGenTextures(1, out texture[0]); // rotate the nbitmap bm.RotateFlip(RotateFlipType.RotateNoneFlipY); // Lock bitmap in rectangle Rectangle rectangle = new Rectangle(0, 0, bm.Width, bm.Height); // Fill the rectangle with a given format BitmapData bitmapData = bm.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); // Tekstur generering Gl.glBindTexture(Gl.GL_TEXTURE_2D, texture[0]); Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGB8, bm.Width, bm.Height, 0, Gl.GL_RGB, Gl.GL_UNSIGNED_BYTE, bitmapData.Scan0); // try Gl.GL_RBG for Gl.GL_RGB above Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR); Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR); if (bm != null) { bm.UnlockBits(bitmapData); // Release bm.Dispose(); // Dispose } } else MessageBox.Show("Ingen bitmap", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); }
Grid
Vi ønsker å tegne ut en buckyball som en enkel grid med kuler i de 60 hjørnene i stedet for å legge tekstur på noen av flatene.
Vi merker oss at siden vi har 12 5-kanter som ikke er naboer, er det tilstrekkelig å tegne kuler i hjørnene på 5-kantene.
Uttegningen kan være som nedenfor. Alle linjer blir tegnet to ganger, men kulene bare en.
static void DrawBuckyBall() { Gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); int mode = Gl.GL_LINE_LOOP; // polygons with 6 edges SetWhiteMaterial(); for (int p = 0; p < 20; p++) { Gl.glBegin(mode); for (int ix = 0; ix < 6; ix++) { Gl.glVertex3f(pol6[p, ix, 0], pol6[p, ix, 1], pol6[p, ix, 2]); } Gl.glEnd(); } // polygons with 5 edges for (int p = 0; p < 12; p++) { Gl.glBegin(mode); for (int ix = 0; ix < 5; ix++) Gl.glVertex3f(pol5[p, ix, 0], pol5[p, ix, 1], pol5[p, ix, 2]); Gl.glEnd(); } // spheres in corners SetBlackMaterial(); Glu.GLUquadric q; q = Glu.gluNewQuadric(); for (int p = 0; p < 12; p++) for (int ix = 0; ix < 5; ix++) { Gl.glPushMatrix(); Gl.glTranslatef(pol5[p, ix, 0], pol5[p, ix, 1], pol5[p, ix, 2]); Glu.gluSphere(q, 0.1, 20, 20); Gl.glPopMatrix(); } Glu.gluDeleteQuadric(q); }