2

Good day,

currently I'm trying to bend a plane to a sphere. I've all ready tried the Mercator projection together with lla to ecef. So the result defers but it isn't like a sphere (half sphere). The most successful variant looked like this (more like a tent, not like a half sphere):

enter image description here

Code for this tent (pastebin). I'm using three.js for rendering.

So I'm asking for some advice. What I'm doing wrong?

Community
  • 1
  • 1
QuentinCaffeino
  • 198
  • 3
  • 14
  • Have you ever tried wrapping a ball with a rectangle piece of paper? That **can't** work, and you need to choose your compromise. – Amit Sep 11 '16 at 20:36
  • I think [this](http://stackoverflow.com/questions/38712632/morphing-sphere-into-plane/38777667#38777667) and [this](http://stackoverflow.com/questions/33065204/how-to-create-sphere-using-multiple-objects/33086141#33086141) answers contain some useful info. – Ramil Kudashev Sep 11 '16 at 21:25

1 Answers1

8

Use spherical coordinate system. The angles long,lat are the 2D linear u,v coordinates in your plane and output is 3D x,y,z.

  1. Convert vertexes (points) of your planar mesh to sphere surface

    I suspect you got points in form (x,y,z) so you need first compute the u,v. Let U,V are perpendicular unit basis vectors lying on the plane. They can be obtained by substracting 2 point on plane mesh , normalizing size and exploiting cross product to ensure perpendicularity. So:

    u = `dot_product((x,y,z),U)` = x*U.x + y*U.y + z*U.z
    v = `dot_product((x,y,z),V)` = x*V.x + y*V.y + z*V.z
    

    Now convert to sphere angles:

    long=6.2831853*u/u_size_of_mesh
    lat =3.1415926*v/v_size_of_mesh
    

    And finally compute new (x,y,z) on the sphere surface:

    x = R*cos(lat)*cos(long)
    y = R*cos(lat)*sin(long)
    z = R*sin(lat)
    
  2. mesh

    The planar mesh must have dense enough points structure (enough triangles/faces) otherwise the sphere would not look as it should. Another problem is that planar mesh does have edges and sphere surface not. Ti s will possibly create seems/gaps on the surface of sphere where the edges of plane connect. If you want to avoid this you can either add faces between edges on opposite sides of plane mesh to fill gaps or fully throw away your mesh and re-sample the plane with uniform grid of points.

    If you want to fully re-sample your mesh then the best you can do is first create regular sphere mesh for example with:

    Sphere triangulation by mesh subdivision

    And then compute the corresponding point on plane by inverse process to #1 so you can interpolate the other parameters of the points (like color, texture coordinate etc)

[Notes]

If you want to animate this then just use linear interpolation between original plane point P0(x,y,z) and corresponding sphere surface point P1(x,y,z) with animation parameter t=<0.0,1.0> like this:

P = P0 + (P1-P0)*t

if t=0 then the output is planar mesh else if t=1 then it is sphere. anywhere in between is the wrapping process so increment t up to 1 with small enough step (like 0.01) and render in some timer ...

[Edit1] U,V basis vectors

The Idea is simple obtain 2 non parallel vectors and change one of them so it is perpendicular to first but still on the same plane.

  1. take any mesh face

    for example triangle ABC

  2. compute 2 nonzero non parallel vectors on the plane

    That is easy just substract any 2 pairs of Vertexes for example:

    U.x=B.x-A.x
    U.y=B.y-A.y
    V.x=C.x-A.x
    V.y=C.y-A.y
    

    and make them unit in size so divide them by their size

    ul=sqrt((U.x*U.x)+(U.y*U.y))
    vl=sqrt((V.x*V.x)+(V.y*V.y))
    U.x/=ul
    U.y/=ul
    V.x/=vl
    V.y/=vl
    
  3. make them perpendicular

    So left one vector as is (for example U) and compute the other so it is perpendicular. For that you can use cross product. Cross product of two unit vectors is new unit vector perpendicular to both. Which one from the 2 possibilities depends only on the order of operands ((U x V) = - (V x U)) so for example:

    // W is perpendicular to U,V
    W.x=(U.y*V.z)-(U.z*V.y)
    W.y=(U.z*V.x)-(U.x*V.z)
    W.z=(U.x*V.y)-(U.y*V.x)
    // V is perpendicular to U,W
    V.x=(U.y*W.z)-(U.z*W.y)
    V.y=(U.z*W.x)-(U.x*W.z)
    V.z=(U.x*W.y)-(U.y*W.x)
    

    The W is just a temporary vector (in the image it is called V') btw it is the normal vector to the surface.

    basis vectors

  4. size and alignment

    Now as I do not have any more info about your mesh I do not know its shape, size etc... The ideal case is if the mesh is rectangular and U,V vectors are aligned to its edges. In such case you just normalize the coordinates by the rectangle size in each direction (as in above image on the left).

    If your mesh is not like this and you are computing U,V from face by this approach then the result may not be aligned to the edges at all (they can be rotated by any angle)...

    non aligned

    If such case can not be avoided (by selecting corners face) then the corner points of your shape will have various coordinate limits along each edges And you need to interpolate or map them to the correct spherical interval in some meaning full way (can't be more specific as I have no Idea what exactly you are doing).

    For almost rectangular shapes is sometimes OK to use the edges as U,V even if they are not perfectly perpendicular to each other.

[Edit2] C++ example

Well if you got perfectly aligned square shape mesh with some noise in Z axis (like height map) then this is how I would do the mesh conversion:

//---------------------------------------------------------------------------
struct _pnt // points
    {
    double xyz[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; };*/
    };
struct _fac // faces (triangles)
    {
    int i0,i1,i2;
    double nor[3];
    _fac(){}; _fac(_fac& a){ *this=a; }; ~_fac(){}; _fac* operator = (const _fac *a) { *this=*a; return this; }; /*_fac* operator = (const _fac &a) { ...copy... return this; };*/
    };
// dynamic mesh
List<_pnt> pnt;
List<_fac> fac;
//---------------------------------------------------------------------------
void mesh_normals() // compute normals
    {
    int i;
    _fac *f;
    double a[3],b[3];
    for (f=&fac[0],i=0;i<fac.num;i++,f++)
        {
        vector_sub(a,pnt[f->i1].xyz,pnt[f->i0].xyz);    // a = pnt1 - pnt0
        vector_sub(b,pnt[f->i2].xyz,pnt[f->i0].xyz);    // b = pnt2 - pnt0
        vector_mul(a,a,b);                              // a = a x b
        vector_one(f->nor,a);                           // nor = a / |a|
        }
    }
//---------------------------------------------------------------------------
void mesh_init()    // generate plane mesh (your square with some z noise)
    {
    int u,v,n=40;   // 40x40 points
    double d=2.0/double(n-1);
    _pnt p;
    _fac f;
    Randomize();
    RandSeed=13;
    // create point list
    pnt.allocate(n*n); pnt.num=0;               // preallocate list size to avoid realocation
    for (p.xyz[0]=-1.0,u=0;u<n;u++,p.xyz[0]+=d) // x=<-1.0,+1.0>
     for (p.xyz[1]=-1.0,v=0;v<n;v++,p.xyz[1]+=d)// y=<-1.0,+1.0>
        {
        p.xyz[2]=0.0+(0.05*Random());           // z = <0.0,0.05> noise
        pnt.add(p);
        }
    // create face list
    vector_ld(f.nor,0.0,0.0,1.0);
    for (u=1;u<n;u++)
     for (v=1;v<n;v++)
        {
        f.i0=(v-1)+((u-1)*n);
        f.i1=(v-1)+((u  )*n);
        f.i2=(v  )+((u-1)*n);
        fac.add(f);
        f.i0=(v  )+((u-1)*n);
        f.i1=(v-1)+((u  )*n);
        f.i2=(v  )+((u  )*n);
        fac.add(f);
        }
    mesh_normals();
    }
//---------------------------------------------------------------------------
void mesh_sphere()  // convert to sphere
    {
    int i;
    _pnt *p;
    double u,v,lon,lat,r,R=1.0;
    // I know my generated mesh is aligned so:
    double U[3]={ 1.0,0.0,0.0 };
    double V[3]={ 0.0,1.0,0.0 };
    for (p=&pnt[0],i=0;i<pnt.num;i++,p++)   // process all points
        {
        // get the u,v coordinates
        u=vector_mul(p->xyz,U);
        v=vector_mul(p->xyz,V);
        // I also know the limits are <-1,+1> so conversion to spherical angles:
        lon=M_PI*(u+1.0);               // <-1.0,+1.0> -> <0.0,6.28>
        lat=M_PI*v*0.5;                 // <-1.0,+1.0> -> <-1.57,+1.57>
        // compute spherical position (superponate z to r preserve noise)
        r=R+p->xyz[2];
        p->xyz[0]=r*cos(lat)*cos(lon);
        p->xyz[1]=r*cos(lat)*sin(lon);
        p->xyz[2]=r*sin(lat);
        }
    mesh_normals();
    }
//---------------------------------------------------------------------------
void mesh_draw()    // render
    {
    int i;
    _fac *f;
    glColor3f(0.2,0.2,0.2);
    glBegin(GL_TRIANGLES);
    for (f=&fac[0],i=0;i<fac.num;i++,f++)
        {
        glNormal3dv(f->nor);
        glVertex3dv(pnt[f->i0].xyz);
        glVertex3dv(pnt[f->i1].xyz);
        glVertex3dv(pnt[f->i2].xyz);
        }
    glEnd();
    }
//---------------------------------------------------------------------------

I used mine dynamic list template so:

  • List<double> xxx; is the same as double xxx[];
  • xxx.add(5); adds 5 to end of the list
  • xxx[7] access array element (safe)
  • xxx.dat[7] access array element (unsafe but fast direct access)
  • xxx.num is the actual used size of the array
  • xxx.reset() clears the array and set xxx.num=0
  • xxx.allocate(100) preallocate space for 100 items

The usage of this is like this:

mesh_init();
mesh_sphere();

Here the results:

overview

On the left is the generated planar mesh with noise and on the right the result after conversion.

The code reflects all the stuff above + add the Z - noise to the sphere radius to preserve the features. Normals are recomputed from the geometry in standard way. For whole TBN matrix you need the connection info from the topology and recompute from that (or exploit the sphere geometry and use TBN from it.

Btw if you want mapping onto sphere instead of mesh conversion you should take a look at related QAs:

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • If it possible I need explanation about this part: _They can be obtained by substracting 2 point on plane mesh , normalizing size and exploiting cross product to ensure perpendicularity_. And this two parametrs: _u_size_of_mesh_ and _v_size_of_mesh_ – QuentinCaffeino Sep 12 '16 at 11:16
  • I've created almost everything except those to parametrs [link](http://pastebin.com/vbxiBSQu). Now i'm getting very large e numbers on long (-6.975736996024239e-28) – QuentinCaffeino Sep 12 '16 at 12:00
  • @Sergey95 added **[edit1]** with requested info. The `-6.975736996024239e-28` is almost zero not a big number at all. Check your `sin,cos` functions if they need radians or degrees. Also check if you are not rounding floating numbers to integers those two are the most often reasons for error – Spektre Sep 12 '16 at 18:02
  • thanks. I've made it. Now one eighth of sphere build from perfectly square plain mesh looks like this: [image](http://i.imgur.com/rtg4prH.png). And the code looks like this: [code](http://pastebin.com/8bkWrMeG) – QuentinCaffeino Sep 15 '16 at 06:21
  • First: Again, thank you very much for all help! Second: And one last question: if you could show me a direction to dig to define how to bend a dynamic mesh. For example: I have a perfect square by X and Y, but Z vertices will be on different positions. what I have is: vertices, tangents and normals list. – QuentinCaffeino Sep 15 '16 at 15:17
  • Oh, I think now I know how to do that. Thanks again! – QuentinCaffeino Sep 16 '16 at 08:05
  • Hey, guys. What is these two parameters: u_size_of_mesh and v_size_of_mesh ? How do you get it? – Vlad May 03 '21 at 15:04
  • 1
    @Vlad its the grid resolution of the planar state grid/mesh ... so you got a rectangular like grid of points. So `u_size_mesh` is number of points per one side and `v_size_mesh` is number of points per the other – Spektre May 03 '21 at 16:33