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!
Quote this message in a reply
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."
Quote this message in a reply
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!
Quote this message in a 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);
            
        }
    }
Quote this message in a reply
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.
Quote this message in a reply
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();
   }
}
Quote this message in a reply
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...
Quote this message in a reply
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.
Quote this message in a reply
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!

Could you please provide the rest of your code? (*.h)
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  how to create a grid / 2D mesh bfarah 1 4,079 Oct 20, 2010 02:22 AM
Last Post: iamflimflam1
  Boolean Mesh Operations and Mesh-Based CSG Oddity007 2 5,672 Feb 13, 2010 03:42 PM
Last Post: Oddity007
  OpenGL ES creating a 2D mesh soulstorm 0 3,000 May 20, 2009 02:37 AM
Last Post: soulstorm
  Breaking down a concave mesh into convex pieces Willem 5 4,664 Aug 10, 2008 05:49 AM
Last Post: Willem
  Mesh Subdivisions KiroNeem 0 3,452 Dec 29, 2005 04:13 PM
Last Post: KiroNeem