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):
Code for this tent (pastebin). I'm using three.js for rendering.
So I'm asking for some advice. What I'm doing wrong?
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
.
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)
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.
take any mesh face
for example triangle ABC
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
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.
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)...
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 listxxx[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 arrayxxx.reset()
clears the array and set xxx.num=0xxx.allocate(100)
preallocate space for 100
itemsThe usage of this is like this:
mesh_init();
mesh_sphere();
Here the results:
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: