This source code was found on the net and is supposedly from the Blender source code.
Code:
float SAacos(float fac)
{
if(fac<= -1.0f)
{
return (float)D3DX_PI;
}
else if(fac>=1.0f)
{
return 0.0f;
} else return (float)acos(fac);
}
void SphereMap(float x, float y, float z, float &u, float &v)
{
float PI=D3DX_PI;
float len;
len = sqrt(x*x+y*y+z*z);
if(len>0.0f)
{
if(x==0.0f && y==0.0f)
{
u = 0.0f; /* othwise domain error */
}
else
{
u = (1.0f - atan2(x,y)/PI)/2;
}
z/=len;
v = 1.0f- SAacos(z)/PI;
}
}
This sphere map code works perfect with no distortions. Make sure your maps are a power of 2 and make sure the width is twice the height or a 2:1 ratio.
Valid 2:1 ratios would be something like 512x256, 128x256, 1024x512, etc.
Very handy for making planets. Extremely simple way to create spheres, planets, etc, is to use D3DXCreateSphere(), clone the mesh to add u,v coords, and use the above code to feed vertices in and get u,v coords out.
Once you have done all this you must do this prior to rendering:
m_pDevice->SetRenderState(D3DRS_WRAP0,D3DWRAP_U);
This tells Direct3D to wrap the texture rendering. If you don't do this, the sphere will have this very ugly crease in it where the texture is attempting to wrap from <1.0f to 0.0f. Without telling Direct3D to treat this as a wrap instead of a complete texture address (.90f to 0.0f in normal mode would be a near complete texture address) it will not wrap the coords.
Do not confuse this wrapping with the wrap mode of the texture stages. Also once you set this make sure you set it back to normal using this:
m_pDevice->SetRenderState(D3DRS_WRAP0,0);
The WRAP0 refers to sampler 0,1,2,3,4....etc, etc. So WRAP2 would be sampler 2.
Why this code works is because acosf() has been wrapped to account for domain errors which occurs at the poles of the spheres. With this code the domain errors are caught and hard-coded values are returned which represent the correct values. Just using acosf() will NOT work.
EDIT:
There is still some distortion at the poles. Back to the drawing board.