Generating a sphere as a 3D mesh

From NoskeWiki
Jump to navigation Jump to search

About

I recently ran into the problem of generating a polygon mesh representation of a sphere when I wanted to convert from a custom 3D program to a Wavefront OBJ file format. Since OBJ file format didn't support primitives, I had no option but to render a sphere as a mesh. Fortunately it's not too difficult to do.


When rendering / generating a sphere as polygons there are two main approaches:

  • (1) Generate a "standard sphere".
    • This approach is much like a globe in that there certain number of vertical lines of latitude and horizontal lines of longitude which break the sphere up into many rectangular (4 sided) parts. Note however there will be a point at the top and bottom (north and south pole) and polygons attached to these will be triangular (3 sided). These are easiest to generate and the number of polygons will be equal to: LONGITUDE_LINES * (LATITUDE_LINES + 1). Of these (LATITUDE_LINES*2) will be triangular and connected to a pole, and the rest rectangular.
  • (2) Generate a "sphere-like polyhedron shape" and recursively subdivide its faces. Such shapes include:
    • Octahedron - 8 identical equilateral triangles - each can be recursively subdivided into 4 triangles by adding 3 new points each time (next one with have 32 triangles).
    • Icosahedron - 20 identical equilateral triangles with 12 vertices - can be recursively subdivided into 4 triangles by adding 3 new points each time (next will have 80 faces and 41 vertices).
    • Hexahedron / Cube - 3 square faces - each can be recursively subdivided into 4 new triangles by adding 5 new points each time.
    • Tetrahedron - 4 equilateral triangular faces - each can be recursively subdivided into 3 triangles each time.
    • And other shapes too, such as a truncated isochedron (think: soccer ball) which combine different sided shapes. These are typically harder to generate.


Generating a Standard Sphere

The code below shows how to generate a standard sphere. This has been written in standard C and results output in Wavefront OBJ file format where indexes start at 1.

#include <stdio.h>
#include <math.h>

typedef struct{
  float x;
  float y;
  float z;
}  Point;

float DEGS_TO_RAD = 3.14159f/180.0f;
int numVertices = 0;    // Tallies the number of vertex points added.

//------------------------
//-- Prints a sphere as a "standard sphere" triangular mesh with the specified
//-- number of latitude (nLatitude) and longitude (nLongitude) lines and
//-- writes results to the specified output file (fout).

void printStandardSphere(Point pt, float radius, int nLatitude, int nLongitude, FILE *fout)
{
  int p, s, i, j;
  float x, y, z, out;
  int nPitch = nLongitude + 1;
  
  float pitchInc = (180. / (float)nPitch) * DEGS_TO_RAD;
  float rotInc   = (360. / (float)nLatitude) * DEGS_TO_RAD;
  
  //## PRINT VERTICES:
  
  fprintf(fout,"v %g %g %g\n", pt.x, pt.y+radius, pt.z);    // Top vertex.
  fprintf(fout,"v %g %g %g\n", pt.x, pt.y-radius, pt.z);    // Bottom vertex.
  numVertices = numVertices+2;
  
  int fVert = numVertices;    // Record the first vertex index for intermediate vertices.
  for(p=1; p<nPitch; p++)     // Generate all "intermediate vertices":
  {
    out = radius * sin((float)p * pitchInc);
    if(out < 0) out = -out;    // abs() command won't work with all compilers
    y   = radius * cos(p * pitchInc);
    printf("OUT = %g\n", out);    // bottom vertex
    printf("nPitch = %d\n", nPitch);    // bottom vertex
    for(s=0; s<nLatitude; s++)
    {
      x = out * cos(s * rotInc);
      z = out * sin(s * rotInc);
      
      fprintf(fout,"v %g %g %g\n", x+pt.x, y+pt.y, z+pt.z);
      numVertices++;
    }
  }
  
  //## PRINT SQUARE FACES BETWEEN INTERMEDIATE POINTS:
  
  for(p=1; p<nPitch-1; p++) {
    for(s=0; s<nLatitude; s++) {
      i = p*nLatitude + s;
      j = (s==nLatitude-1) ? i-nLatitude : i;
      fprintf(fout,"f %d %d %d %d\n", 
              (i+1-nLatitude)+fVert, (j+2-nLatitude)+fVert, (j+2)+fVert, (i+1)+fVert);
    }
  }
  
  //## PRINT TRIANGLE FACES CONNECTING TO TOP AND BOTTOM VERTEX:
  
  int offLastVerts  = fVert + (nLatitude * (nLongitude-1));
  for(s=0; s<nLatitude; s++)
  {
    j = (s==nLatitude-1) ? -1 : s;
    fprintf(fout,"f %d %d %d\n", fVert-1, (j+2)+fVert,        (s+1)+fVert       );
    fprintf(fout,"f %d %d %d\n", fVert,   (s+1)+offLastVerts, (j+2)+offLastVerts);
  }
}




//------------------------
//-- Entry point. This main() function demonstrates how you can
//-- use "printStandardSphere()", but you probably won't
//-- want/need to copy it in your own code.

int main(int argc, char *argv[])
{  
  int nLatitude  = 8;                  // Number vertical lines.
  int nLongitude = nLatitude / 2;      // Number horizontal lines.
  // NOTE: for a good sphere use ~half the number of longitude lines than latitude.
  Point centerPt;            // Position the center of out sphere at (0,0,0).
  
  if (argc < 2) {
    fprintf(stderr, "Must enter: './programname outputfile.obj'\n");
    return (-1);
  }
  
  FILE *fout = fopen(argv[1] , "w");
  if (fout == NULL) {
     printf("Couldn't open output file %s.\n", argv[1]);
     return (-1);
  }
  printStandardSphere(centerPt, 10., nLatitude, nLongitude, fout);      // Print sphere with radius 10 into file.
  fclose(fout);
  fprintf(stdout, "  # vertices:   %d\n", numVertices);
  return (0);
}

Also note that we've included a main, but for your own code you'll probably just want to copy the contents of the "printStandardSphere()" function. To compile the code above you could save it as "writespheres.c" and then execute the follow lines to compile and run it:

> gcc -o spheresprogram writespheres.c
> ./spheresprogram testoutput.obj


Generating and Isohedron

The code below represents a single icosahedron with 20 identical equilateral triangular faces, 30 edges and 12 vertices. Rather than generate this shape, the coordinates have already been hardcoded. This is a faster option, but if you want to recursively subdivide faces or generate different "sphere like polyhedral shapes" then read on here: Regular Polyhedron Generators.


//------------------------
//-- Prints a sphere as an isohedron - a regular polyedron with 20 identical
//-- equilateral triangular faces, 30 edges and 12 vertices.
//-- Code was modified from:
//-- http://www.csee.umbc.edu/~squire/reference/polyhedra.shtml#icosahedron

static void printSphereAsIcosahedron(Ipoint pt, float radius, FILE *fout)
{
  int i;
  Ipoint v[12];
  
  v[0].x = 0.000;    v[0].y = 1.000;    v[0].z = 0.000;    // Top-most point.
  v[1].x = 0.894;    v[1].y =  0.447;   v[1].z = 0.000;
  v[2].x = 0.276;    v[2].y =  0.447;  v[2].z = 0.851;
  v[3].x = -0.724;  v[3].y =  0.447;  v[3].z = 0.526;
  v[4].x = -0.724;  v[4].y =  0.447;  v[4].z = -0.526;
  v[5].x = 0.276;    v[5].y =  0.447;  v[5].z = -0.851;
  v[6].x = 0.724;    v[6].y = -0.447;  v[6].z = 0.526;
  v[7].x = -0.276;  v[7].y = -0.447;  v[7].z = 0.851;
  v[8].x = -0.894;  v[8].y = -0.447;  v[8].z = 0.000;
  v[9].x = -0.276;  v[9].y = -0.447;  v[9].z = -0.851;
  v[10].x= 0.724;    v[10].y= -0.447;  v[10].z= -0.526;
  v[11].x= 0.000;    v[11].y= -1.000;  v[11].z= 0.000;    // Bottom-most point.
  
  //## PRINT VERTICES:
  for(i=0; i<12; i++)
  {
    fprintf(fout,"v %.5g %.5g %.5g\n", v[i].x*radius+pt.x, v[i].y*radius+pt.y, v[i].z*radius+pt.z);
  }
  
  //## PRINT FACES:
  fprintf(fout,"f -12 -10 -11\n");  // |-- Top-most triangles.
  fprintf(fout,"f -12 -9 -10\n");   // |
  fprintf(fout,"f -12 -8 -9\n" );   // |
  fprintf(fout,"f -12 -7 -8\n" );   // | 
  fprintf(fout,"f -12 -11 -7\n");   // |
  fprintf(fout,"f -1 -6 -5\n"  );     // |-- Bottom-most triangles.
  fprintf(fout,"f -1 -5 -4\n"  );     // |
  fprintf(fout,"f -1 -4 -3\n"  );     // |
  fprintf(fout,"f -1 -3 -2\n"  );     // |
  fprintf(fout,"f -1 -2 -6\n"  );     // |
  fprintf(fout,"f -11 -10 -6\n");   // |-- Downwards pointing
  fprintf(fout,"f -10 -9 -5\n" );   // |   triangles.
  fprintf(fout,"f -9 -8 -4\n"  );   // |
  fprintf(fout,"f -8 -7 -3\n"  );   // |
  fprintf(fout,"f -7 -11 -2\n" );   // |
  fprintf(fout,"f -6 -10 -5\n" );     // |-- Upwards pointing
  fprintf(fout,"f -5 -9 -4\n"  );     // |   triangles.
  fprintf(fout,"f -4 -8 -3\n"  );     // |
  fprintf(fout,"f -3 -7 -2\n"  );     // |
  fprintf(fout,"f -2 -11 -6\n" );     // |
}

Note: this code block was adapted from here and modified to output OBJ format. Note that in the OBJ format the front of faces is defined in an counter-clockwise order.



Links