## Spherical Mesh.

dave05
Unregistered

Post: #1
Hi !

For my next openGL hurdle, I'm making the jump to 3 dimensional and pseudo-3D graphics. For now, all I need the game to be able to draw is texture-mapped planes and spheres, the former of which I can handle without trouble.

Anyways, whipping out my first-second year calculus book, I found the section on spherical coordinates (who knew polar coordinates would ACTUALLY be practical).

I came up with the following code for initializing a quadrilateral mesh for a sphere. Keep in mind I want the spheres in my game to appear as rounded as possible. This one has 36*16 + 2 vertices = 578 vertices. I'm not sure how to convert that to "number of polygons," but I know there's a simple formula out there.

Initialization code:

Code:
```#define kZAngleConstant -80 #define kXAngleConstant 0 #define kZAngleIncrementPerIndex 10.00f #define kXAngleIncrementPerIndex 10.00f void SphereMap_Init (void) {     int        map_x, map_y;     GLfloat        phi, theta, r;          r = 1.0f;          pm.south_Pt.x =     pm.south_Pt.y = 0;     pm.south_Pt.z = -1.0f;          pm.south_Pt.x =     pm.south_Pt.y = 0;     pm.south_Pt.z = 1.0f;          for (map_y = 0; map_y < kSphereMap_Height; map_y ++) {                  // -80 deg <= phi <= 80 deg         phi = kZAngleConstant + kZAngleIncrementPerIndex * map_y;                  for (map_x = 0; map_x < kSphereMap_Width; map_x ++) {                          // 0 deg <= theta <= 350 deg             theta = kXAngleConstant + kXAngleIncrementPerIndex * map_x;                          pm.texMap_pt [map_x][map_y].x = r * sin(phi) * cos(theta);             pm.texMap_pt [map_x][map_y].y = r * sin(phi) * sin(theta);             pm.texMap_pt [map_x][map_y].z = r * cos(phi);         }    // set appropriate vertices     } }```

so what I do is set the sphere's bottom and top "axis" points, or north and south points as I call them. Then at ten degree increments, I set the appropriate 3 dimensional pixel coordinates.

for drawing, I'm thinking I should first draw triangles from the north and south points to their respective "nearest meridians," then take all (map_x, map_y) points up to the second last, and draw quadrilaterals at indexes (map_x, map_y) = {(x,y); (x+1,y); (x,y+1); (x+1,y+1)}.

I haven't yet tested this, and I'm afraid to because I expect there is something fundamentally wrong with my approach. Any suggestions / criticism and / or sample code are greatly appreciated!
Moderator
Posts: 529
Joined: 2003.03
Post: #2
gluSphere?

"Yes, well, that's the sort of blinkered, Philistine pig-ignorance I've come to expect from you non-creative garbage."
Sage
Posts: 1,403
Joined: 2005.07
Post: #3
Code:
```    pm.south_Pt.x = // Theres no value here, and no semi colon.     pm.south_Pt.y = 0;     pm.south_Pt.z = -1.0f;          pm.south_Pt.x =     pm.south_Pt.y = 0;     pm.south_Pt.z = 1.0f; // You just set this to -1?!?!?```

Code:
```    pm.texMap_pt [map_x][map_y].x = r * sin(phi) * cos(theta);     pm.texMap_pt [map_x][map_y].y = r * sin(phi) * sin(theta);     pm.texMap_pt [map_x][map_y].z = r * cos(phi);```
Are you using a 3D texture map?

Code:
```for (map_x = 0; map_x < kSphereMap_Width; map_x ++) {         // 0 deg <= theta <= 350 deg     theta = kXAngleConstant + kXAngleIncrementPerIndex * map_x;```
Id suggest that you loop with theta, somthing like
Code:
`for (theta = 0; theta <= 350; theta += 360/N_slices) {`
and then calculate the 2D texture coords, from theta and phi
Code:
`glTexCoord2f(theta, phi); glVertex3f(.....);`
Would do it alright.

Sir, e^iÏ€ + 1 = 0, hence God exists; reply!
jamiep
Unregistered

Post: #4
This is my code for defining a spherical mesh. Note I define three new vertices for each triangle in the mesh. It would be nicer to define each vertex once and just assign vertex indices to the triangles.

Code:
```    int i,j;     float vert1[3], vert2[3], vert3[3];     float theta1,theta2,phi1,phi2,dtheta,dphi;          dtheta = PI/((float)Ntheta+1);     dphi = 2.0*PI/((float)Nphi);              /* top cap triangles */         for (j=0; j<Nphi; j++) {         vert1[0] = 0.0;         vert1[1] = 0.0;         vert1[2] = radius;                  vert2[0] = radius*sin(dtheta)*cos(j*dphi);         vert2[1] = radius*sin(dtheta)*sin(j*dphi);         vert2[2] = radius*cos(dtheta);                  vert3[0] = radius*sin(dtheta)*cos((j+1)*dphi);         vert3[1] = radius*sin(dtheta)*sin((j+1)*dphi);         vert3[2] = radius*cos(dtheta);             }          /* bottom cap triangles */     for (j=0; j<Nphi; j++) {                 vert1[0] = 0.0;         vert1[1] = 0.0;         vert1[2] = -radius;                  vert2[0] = radius*sin(PI-dtheta)*cos(j*dphi);         vert2[1] = radius*sin(PI-dtheta)*sin(j*dphi);         vert2[2] = radius*cos(PI-dtheta);                  vert3[0] = radius*sin(PI-dtheta)*cos((j+1)*dphi);         vert3[1] = radius*sin(PI-dtheta)*sin((j+1)*dphi);         vert3[2] = radius*cos(PI-dtheta);             }          /* bulk of sphere */     for (i=1; i<Ntheta+1; i++) {         for (j=0; j<Nphi; j++) {                          theta1 = (float)i*dtheta;             phi1 = (float)j*dphi;             theta2 = (float)(i+1)*dtheta;             phi2 = (float)(j+1)*dphi;                          /* triangle 1 */             vert1[0] = radius*sin(theta1)*cos(phi1);             vert1[1] = radius*sin(theta1)*sin(phi1);             vert1[2] = radius*cos(theta1);                          vert2[0] = radius*sin(theta2)*cos(phi1);             vert2[1] = radius*sin(theta2)*sin(phi1);             vert2[2] = radius*cos(theta2);                          vert3[0] = radius*sin(theta2)*cos(phi2);             vert3[1] = radius*sin(theta2)*sin(phi2);             vert3[2] = radius*cos(theta2);                              /* triangle 2 */             vert1[0] = radius*sin(theta1)*cos(phi1);             vert1[1] = radius*sin(theta1)*sin(phi1);             vert1[2] = radius*cos(theta1);                          vert2[0] = radius*sin(theta2)*cos(phi2);             vert2[1] = radius*sin(theta2)*sin(phi2);             vert2[2] = radius*cos(theta2);                          vert3[0] = radius*sin(theta1)*cos(phi2);             vert3[1] = radius*sin(theta1)*sin(phi2);             vert3[2] = radius*cos(theta1);                      }     }```
Sage
Posts: 1,199
Joined: 2004.10
Post: #5
Recursive subdivision of an isocahedron works beautifully -- plus you get even distribution of identically sized triangles. No loss of precision as you approach the poles.

It's in _The Red Book_ but I can post code if you're interested.
Sage
Posts: 1,066
Joined: 2004.07
Post: #6
Along the lines of making spheres, can someone help me identify what PID2 means in the following code? I figure TWOPI is simply 2 * M_PI, but PID2 still gets me. I know this isn't an efficient way of making spheres (unless calling it in a display list and using that), but I just want to make a sphere. I've identified where the PID2 shows up using a <------- after the line. This might also help Dave out with figuring out if his code will work by comparing it to this (though I can't be sure this works until I figure out that PID2 variable).
http://astronomy.swin.edu.au/~pbourke/opengl/sphere/ Wrote:
Code:
```/*    Create a sphere centered at c, with radius r, and precision n    Draw a point for zero radius spheres */ void CreateSphere(XYZ c,double r,int n) {    int i,j;    double theta1,theta2,theta3;    XYZ 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 * TWOPI / n - PID2;               <-------       theta2 = (j + 1) * TWOPI / n - PID2;      <-------       glBegin(GL_QUAD_STRIP);       for (i=0;i<=n;i++) {          theta3 = i * TWOPI / n;          e.x = cos(theta2) * cos(theta3);          e.y = sin(theta2);          e.z = cos(theta2) * sin(theta3);          p.x = c.x + r * e.x;          p.y = c.y + r * e.y;          p.z = 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.x = cos(theta1) * cos(theta3);          e.y = sin(theta1);          e.z = cos(theta1) * sin(theta3);          p.x = c.x + r * e.x;          p.y = c.y + r * e.y;          p.z = 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();    } }```
Sage
Posts: 1,199
Joined: 2004.10
Post: #7
Perhaps PI divided by two? See if you can't find where PID2 is defined...

Anyhow, here's how I do it ( I store it in a display list, since the actual generation is recursive and can't take advantage of triangle strips ):
Code:
```/*     X, Z, vdata, tindices -- initial data set for isocahedron -- subdivision will     allow for arbitrarily detailed sphere     BarrelTexCoord{XAxis|ZAxis|YAxis } Texture coordinate generation for barrel wrap     over sphere, along specified axis. */ #define X .525731112119133606 #define Z .850650808352039932 static float vdata[12][3] = {     {-X, 0.0, Z}, {X, 0.0, Z}, {-X, 0.0, -Z}, {X, 0.0, -Z},     {0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, {0.0, -Z, -X},     {Z, X, 0.0}, {-Z, X, 0.0}, {Z, -X, 0.0}, {-Z, -X, 0.0} }; static GLint tindices[20][3] = {     {0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},     {8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},     {7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6},     {6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };     void SphereGeometry::subdivide(float *v1, float *v2, float *v3, long depth) {     float v12[3], v23[3], v31[3];          if (depth == 0)     {         vec3 a, b, c;         vec3 n0, n1, n2;         a.x = _radius * v1[0];         a.y = _radius * v1[1];         a.z = _radius * v1[2];         b.x = _radius * v2[0];         b.y = _radius * v2[1];         b.z = _radius * v2[2];         c.x = _radius * v3[0];         c.y = _radius * v3[1];         c.z = _radius * v3[2];                           n0.x = a.x;         n0.y = a.y;         n0.z = a.z;         n1.x = b.x;         n1.y = b.y;         n1.z = b.z;         n2.x = c.x;         n2.y = c.y;         n2.z = c.z;                  vec2 ta, tb, tc;         switch( textureCoordGeneration() )         {             case Barrel_X:                 BarrelTexCoordXAxis( c, b, a, _radius, tc, tb, ta );                             break;                              case Barrel_Y:                 BarrelTexCoordYAxis( c, b, a, _radius, tc, tb, ta );                             break;                          case Barrel_Z:                 BarrelTexCoordZAxis( c, b, a, _radius, tc, tb, ta );                             break;                              default:                 /*                     Not a recognized texgen, so just pump the triangle and                     get out of here -- note the Bad Form of a return in a switch                 */                 triangle( c, b, a, n2, n1, n0 );                 return;         }         TriangleTexture tex( tc, tb, ta );             triangle( c, b, a, n2, n1, n0, NULL, &tex );                  return;     }     for ( int i = 0; i < 3; i++)     {         v12[i] = v1[i]+v2[i];         v23[i] = v2[i]+v3[i];         v31[i] = v3[i]+v1[i];     }     normalize(v12);     normalize(v23);     normalize(v31);     subdivide(v1, v12, v31, depth-1 );     subdivide(v2, v23, v12, depth-1 );     subdivide(v3, v31, v23, depth-1 );     subdivide(v12, v23, v31, depth-1 ); }```

And to actually make it happen:

Code:
```    int depth = 2;     for ( int i = 0; i < 20; i++)     {         subdivide( &vdata[tindices[i][0]][0],                    &vdata[tindices[i][1]][0],                    &vdata[tindices[i][2]][0],                    depth );     }```

Disregard the texture stuff...
Sage
Posts: 1,066
Joined: 2004.07
Post: #8
It is indeed M_PI / 2. It's used to adjust the texture as the way the code does it results in the poles being around the equator. By subtracting pi/2 it fixes this problem. That code I posted does in fact work now that I know that. Thanks.
Nibbie
Posts: 1
Joined: 2008.10
Post: #9
Hi TomorrowPlusX.
I too am trying to get up the steep learning curve of OpenGL ES for iPhone development.
I'm embarrassed to say I have little Objective-C experience too.

My first step is to create a rotating smooth sphere (subdivided icosahedron) with a tennis-ball texture and then build from there. I can get an icosahedron to rotate, but I'm stumped when it comes to 1) subdividing it to smooth it out, and then assuming I can get it to appear 2) apply a texture to it.

I'm pulling my hair out trying to get your code to work - I just get a black screen when trying to subdivide!