Turning a cylinder into a sphere without pinching at the poles

I'm working on generating a planet made out of a hexagon grid. The poles aren't needed - making this a bit easier. Is there a better way to turn the cylinder into a sphere that would have uniform hexagons/triangles?

Here's the desired steps:

  1. Generate a 2D plane of hexagons (ok)
  2. Turn the plane into a Cylinder (ok)
  3. Turn the Cylinder into a Sphere / Geosphere (kind of works)

For step 2, I'm just using Sin and Cos to move the vertices into a circular shape. For step 3, right now I'm just using: vertices[i] = vertices[i].normalized * radius;

Image to visualize the problem as it is currently.

Note that the poles are cut off on purpose. The red parts show what one hexagon mesh looks like. I would have to keep them roughly the same size and orientation since they are used for gameplay and visual elements. Each hex has a list of neighbors and works like a graph basically.


Solution 1:

Instead of cylinder to sphere mapping I would do a sphere triangulation...

  1. I would first start with 2 hexagons

    each start at pole and end on the equator or do the half only and mirror the other when all done...

  2. then recursively subdivide the triangles

    so divide lines to half and change the mid point coordinate to align with sphere surface. This will create triangulated sphere. Subdivide to valid number of points to form hexagons and have enough grid points.

  3. change the hexagon mid-point coordinates back to plane of hexagon

    So take the other 6 point and compute average coordinates which gives you the point for middle ...

Something like this:

hexagonation

for more ideas look here:

  • Location of highest density on a sphere
  • Make a sphere with equidistant vertices

[edit1] triangulation (no hexagon corrections)

//---------------------------------------------------------------------------
#include <math.h>
#include "list.h"
class mesh
    {
public:
    class _pnt { public: double p[3]; _pnt(){}; _pnt(_pnt& a){ *this=a; }; ~_pnt(){}; _pnt* operator = (const _pnt *a) { *this=*a; return this; }; /*_pnt* operator = (const _pnt &a) { ...copy... return this; };*/ };
    class _fac { public: int i0,i1,i2; _fac(){}; _fac(_fac& a){ *this=a; }; ~_fac(){}; _fac* operator = (const _fac *a) { *this=*a; return this; }; /*_fac* operator = (const _fac &a) { ...copy... return this; };*/ };
    List<_pnt> pnt; // mesh points
    List<_fac> fac; // mesh triangles

    mesh()      {}
    mesh(mesh& a)   { *this=a; }
    ~mesh() {}
    mesh* operator = (const mesh *a) { *this=*a; return this; }
    //mesh* operator = (const mesh &a) { ...copy... return this; }

    void draw();            // draws the mesh with OpenGL
    void sphere(int n);     // init mesh with unit sphere from triangles (n recursion layers)
    };
//---------------------------------------------------------------------------
void mesh::draw()
    {
    int i;
    _fac *f;
    // fill
    glColor3f(0.7,0.7,0.7);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glDepthFunc(GL_LEQUAL);
    glBegin(GL_TRIANGLES);
    for (i=0,f=fac.dat;i<fac.num;i++,f++)
        {
        glVertex3dv(pnt.dat[f->i0].p);
        glVertex3dv(pnt.dat[f->i1].p);
        glVertex3dv(pnt.dat[f->i2].p);
        }
    glEnd();
    // wireframe
    glColor3f(0.1,0.3,0.7);
    glLineWidth(2.0);
    for (i=0,f=fac.dat;i<fac.num;i++,f++)
        {
        glBegin(GL_LINE_LOOP);
        glVertex3dv(pnt.dat[f->i0].p);
        glVertex3dv(pnt.dat[f->i1].p);
        glVertex3dv(pnt.dat[f->i2].p);
        glEnd();
        }
    glLineWidth(1.0);
    }
//---------------------------------------------------------------------------
void mesh::sphere(int n)
    {
    // init 2 hexagons
    int i,j,m,i0,i1,i2,j0,j1,j2;
    double a,da=M_PI/3.0;
    double *p0,*p1;
    _pnt p;
    _fac f,*g;
    p.p[0]= 0.0;
    p.p[1]= 0.0;
    p.p[2]=+1.0;
    pnt.add(p);
    p.p[2]=-1.0;
    pnt.add(p);
    for (i=0,a=0.0;i<6;i++,a+=da)
        {
        p.p[0]=cos(a);
        p.p[1]=sin(a);
        p.p[2]= 0.0;
        pnt.add(p);
        }
    // top half
    f.i0=0; f.i1=2; f.i2=3; fac.add(f);
    f.i0=0; f.i1=3; f.i2=4; fac.add(f);
    f.i0=0; f.i1=4; f.i2=5; fac.add(f);
    f.i0=0; f.i1=5; f.i2=6; fac.add(f);
    f.i0=0; f.i1=6; f.i2=7; fac.add(f);
    f.i0=0; f.i1=7; f.i2=2; fac.add(f);
    // botom half
    f.i0=1; f.i1=3; f.i2=2; fac.add(f);
    f.i0=1; f.i1=4; f.i2=3; fac.add(f);
    f.i0=1; f.i1=5; f.i2=4; fac.add(f);
    f.i0=1; f.i1=6; f.i2=5; fac.add(f);
    f.i0=1; f.i1=7; f.i2=6; fac.add(f);
    f.i0=1; f.i1=2; f.i2=7; fac.add(f);
    // subdivide triangles
    for (;n>0;n--)              // recursion layers
     for (m=fac.num,i=0;i<m;i++)// scan through all original faces
        {
        g=&fac[i];
        // point indexes
        i0=g->i0; j0=pnt.num;   //     i0
        i1=g->i1; j1=j0+1;      //   j0  j2
        i2=g->i2; j2=j0+2;      // i1  j1  i2
        // genere mid points + sphere surface correction distance from (0,0,0) must be 1.0 (radius)
        for (j=0;j<3;j++) p.p[j]=0.5*(pnt[i0].p[j]+pnt[i1].p[j]); a=1.0/sqrt((p.p[0]*p.p[0])+(p.p[1]*p.p[1])+(p.p[2]*p.p[2])); for (j=0;j<3;j++) p.p[j]*=a; pnt.add(p);
        for (j=0;j<3;j++) p.p[j]=0.5*(pnt[i1].p[j]+pnt[i2].p[j]); a=1.0/sqrt((p.p[0]*p.p[0])+(p.p[1]*p.p[1])+(p.p[2]*p.p[2])); for (j=0;j<3;j++) p.p[j]*=a; pnt.add(p);
        for (j=0;j<3;j++) p.p[j]=0.5*(pnt[i2].p[j]+pnt[i0].p[j]); a=1.0/sqrt((p.p[0]*p.p[0])+(p.p[1]*p.p[1])+(p.p[2]*p.p[2])); for (j=0;j<3;j++) p.p[j]*=a; pnt.add(p);
        // change original fac
        g->i0=j0; g->i1=j1; g->i2=j2;
        //  add 3 x fac
        f.i0=i0; f.i1=j0; f.i2=j2; fac.add(f);
        f.i0=j0; f.i1=i1; f.i2=j1; fac.add(f);
        f.i0=j2; f.i1=j1; f.i2=i2; fac.add(f);
        }
    }
//---------------------------------------------------------------------------

Was a bit curious so I tried to encode this usage is simple:

mesh obj;       // somewhere global...
obj.sphere(3); // init (call once or on change of n...) 
obj.draw();    // inside your gl draw scene routine/event...

So here is the result overview

result

top and bottom poles looks good enough, some distortion is present along the equators but partially it can be caused also by fish-eye. If the initial shape is feed with closer to wanted result start geometry then it may have much better results