[Request for help] Texture mapping a subdivided icosahedron

Started by
21 comments, last by IainC 21 years, 6 months ago
Hi all, I'm using a subdivided icosahedron as an approximation to a sphere; I'm doing it this way instead of using a quadric because I'm going to want to deform the sphere and this method gives me uniform point distribution. The technique is described in example 2-13 of the Red Book. The problem I'm having is texturing the thing. I want to be able to pass a function the normal of the current point and have it pass back appropriate UV coords.
          
// ******************** DEFINES ********************


#define X_BASE 0.525731112119133606f
#define Z_BASE 0.850650808352039932f

// ******************** STRUCTS ********************


struct Vector3f {
	GLfloat x, y, z;

	void Set(GLfloat m_x, GLfloat m_y, GLfloat m_z) {
		x = m_x; y = m_y; z = m_z;
	}
};

// ******************** CLASS DEFINITION ********************


class Icosahedron {
public:
	Icosahedron();
	~Icosahedron();

	void Init(GLfloat m_radius, int subdivisions);
	void Render();

private:
	void Normalize(float v[3]);
	void DrawTriangle(float *v1, float *v2, float *v3);
	void Subdivide(float *v1, float *v2, float *v3, long depth);
	void GetTextureCoord(ASEVector3f *normal, float *targetU, float *targetV);

	GLuint displayList;
	GLfloat radius;
};

// ******************** CLASS IMPLEMENTATION ********************


GLfloat vdata[12][3] = {
  {-X_BASE,0.0,Z_BASE},{X_BASE,0.0,Z_BASE},{-X_BASE,0.0,-Z_BASE},{X_BASE,0.0,-Z_BASE},
  {0.0,Z_BASE,X_BASE},{0.0,Z_BASE,-X_BASE},{0.0,-Z_BASE,X_BASE},{0.0,-Z_BASE,-X_BASE},
  {Z_BASE,X_BASE,0.0},{-Z_BASE,X_BASE,0.0},{Z_BASE,-X_BASE,0.0},{-Z_BASE,-X_BASE,0.0}};

GLuint tindices[20][3] = {
  {1,4,0},{4,9,0},{4,5,9},{8,5,4},{1,8,4},
  {1,10,8},{10,3,8},{8,3,5},{3,2,5},{3,7,2},
  {3,10,7},{10,6,7},{6,11,7},{6,0,11},{6,1,0},
  {10,1,6},{11,0,9},{2,11,9},{5,2,9},{11,2,7}};

Icosahedron::Icosahedron() {
}

Icosahedron::~Icosahedron() {
}

void Icosahedron::Init(GLfloat m_radius, int subdivisions) {
	radius = m_radius;
	displayList = glGenLists(1);
	glNewList(displayList, GL_COMPILE);
	for (int i = 0; i < 20; i++) {
	   Subdivide(&vdata[tindices[i][0]][0], &vdata[tindices[i][1]][0], &vdata[tindices[i][2]][0], subdivisions);
	}
	glEndList();
}

void Icosahedron::Render() {
	glScalef(radius, radius, radius);
	glCallList(displayList);
}

void Icosahedron::DrawTriangle(float *v1, float *v2, float *v3) {
	Vector3f point;
	GLfloat texU, texV;

	glBegin(GL_TRIANGLES);
		point.Set (v1[0], v1[1], v1[2]);
		glNormal3f (point.x, point.y, point.z);		// (Points are already normalised)

		GetTextureCoord (&point, &texU, &texV);
		glTexCoord2f(texU, texV);
		glVertex3f (point.x, point.y, point.z);

		point.Set (v2[0], v2[1], v2[2]);
		glNormal3f (point.x, point.y, point.z);
		GetTextureCoord (&point, &texU, &texV);
		glTexCoord2f(texU, texV);
		glVertex3f (point.x, point.y, point.z);

		point.Set (v3[0], v3[1], v3[2]);
		glNormal3f (point.x, point.y, point.z);
		GetTextureCoord (&point, &texU, &texV);
		glTexCoord2f(texU, texV);
		glVertex3f (point.x, point.y, point.z);
	glEnd();
}

void Icosahedron::GetTextureCoord(ASEVector3f *normal, float *targetU, float *targetV) {
	// ****************************************

	// ****************************************

	// IT'S THIS FUNCTION I CAN'T GET TO WORK!!

	// ****************************************

	// ****************************************

}

void Icosahedron::Normalize(float v[3]) {
   GLfloat d = (float)sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
   if (d == 0.0) return;
   v[0] /= d; v[1] /= d; v[2] /= d;
}

void Icosahedron::Subdivide(float *v1, float *v2, float *v3, long depth) {
   GLfloat v12[3], v23[3], v31[3];
   GLint i;

   if (depth == 0) {
      DrawTriangle(v1, v2, v3);
      return;
   }
   for (i = 0; i < 3; i++) {
      v12[i] = v1[i]+v2[i];
      v23[i] = v2[i]+v3[i];
      v31[i] = v3[i]+v1[i];
   }
   Normalize(v12);
   Normalize(v23);
   Normalize(v31);
   Subdivide(v1, v12, v31, depth-1);
   Subdivide(v2, v23, v12, depth-1);
   Subdivide(v3, v31, v23, depth-1);
   Subdivide(v12, v23, v31, depth-1);
}

  
Any thoughts? Thanks in advance for any help anyone can provide! Brgrds, IainC www.coldcity.com code, pics, life Grr I can't make the source tag keep my formatting... [edited by - IainC on September 27, 2002 6:00:20 AM]
[size="2"]www.coldcity.com code, art, life
Advertisement
Convert your normals from Cartesian coordinates (x, y, z) to Spherical coordinates (rho, theta, phi) and then use theta and phi as your U and V coordinates. Note that during the conversion, rho should be the constant 1 if your normals are already normalized.

Note: you may need some extra work to get seamless texturing in the top/down/left/right edges of the texture.
vincoof,

I already tried that, with the following:


  void Icosahedron::GetTextureCoord(Vector3f *normal, float *targetU, float *targetV) {	*targetV = (float)atan(normal->y/normal->x);	*targetU = (float)(sqrt((normal->x*normal->x) + (normal->y*normal->y)))/normal->z;}  


But no joy.. I was using a texture that should have worked seamlessly:



But I just got horrible effects:



I wonder if maybe my texture options are at fault; I''m using:


  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexImage2D(GL_TEXTURE_2D, 0, 4, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);  


(apologies if posting pictures is too much for people''s bandwidth)

Any further ideas??

Many thanks for your help.
[size="2"]www.coldcity.com code, art, life
I know the first thing someone''s going to say is "use GL_CLAMP, your texture is repeating" but the effect I get then is just as disturbing
[size="2"]www.coldcity.com code, art, life
Ok, can't guarantee this'll work since i'm working by observation (of the pictures you posted) and paper, but try this for your method:

    void Icosahedron::GetTextureCoord(Vector3f *normal, float *targetU, float *targetV){   float normalisedX = 0;   float normalisedZ = -1;   if (((normal->x * normal->x) + (normal->z * normal->z)) > 0){      normalisedX = (normal->x * normal->x) / ((normal->x * normal->x) + (normal->z * normal->z));      normalisedZ = (normal->z * normal->z) / ((normal->x * normal->x) + (normal->z * normal->z));   }   if (normalisedZ == 0){      *targetU = ((normalisedX * PI) / 2);   }   else {      *targetU = atan(normalisedX / normalisedZ);      if (normalisedZ < 0){         *targetU += PI;      }      if (*targetU < 0){         *targetU += 2 * PI;      }   }   *targetU /= 2 * PI;   *targetV = (-normal->y + 1) / 2;}    


Explanation:

First, moving radially around the planet we want to map texture coords so that at longitude 0º we have texture coord U = 0, and at longitude xº we have texture coord U = x/360. This is independant of latitude, so we ignore the normal's y component and re-normalise the x/z components. We also want a default value for the north and south poles so the these points map to the top centre and bottom centre of the texture.

Next we need to map to the latitude. The texture you provided appears to be distorted such that it could be projected directly onto a globe. That is to say that the equator is expanded and the poles are reduced such that the equators, when projected, would retain their size due to the relative flatness of the equator while the poles would be stretched. This means that y coordinates can be mapped very easily simply by mapping the -1 -> 1 range linearly onto a 0 -> 1 range.

As I said, I haven't tested this but give it a go and see what it does!

Enigma

EDIT: spelling

[edited by - enigma on September 27, 2002 8:54:32 AM]
IainC: I''m afraid that your equation is not correct, especially because you should have taken care of the fact that texture coordinates should be expressed in the [0,1] range. For instance, atan returns a value in the [-pi/2,+pi/2] range !
Anyway, you invented the wave effect. That''s cool enough !

Enigma: your equations seem correct. Even though I think it still does not correct seams, it should give better results than above.
BTW, you''re calling glTexParameter and glTexImage2D correctly. That is not where the problem comes from.
vincoof: Ah, of course! I see what you mean about the range being wrong now I look at it. Thanks for checking my texture settings.

Enigma: Thanks a lot for providing that code - I''ll be damn impressed if it works, I can''t code anything without at least a brief compile/crash/debug cycle Great explanation too.

Now I''ve looked at the maths in more detail I agree that Enigma''s code looks good - will go try it and post the result.
[size="2"]www.coldcity.com code, art, life
Sorry for the big image!

Enigma''s function:


Hmmm - It''s certainly a lot closer.

I''ve put some axes on the pics, R=x, G=y, B=z.

Notice that the polar regions are in the right place - getting some wierd kaleidoscope-esque repetition and distortion.

The texturemap''s being applied at least partially correctly, it''s just not falling over the points quite as it should...

I tried editing my original function so that the [-pi/2, pi/2] range was scaled to [0, 1] but the results were remarkably similar to Enigma''s results - there''s something more wrong than just not lining up seamlessly, due to the wierd tesselations.

Hmmmm...

Any further thoughts guys? Really appreciate the help from you both.

www.coldcity.com
code, pics, life
[size="2"]www.coldcity.com code, art, life
I think all the problems lie in the renormalisation I did. Firstly, I forgot to square root after squaring and dividing, so the magnitudes would be slightly out. I also didn''t take into account the direction after renormalising - I just used the magnitude. Try this instead:

  void Icosahedron::GetTextureCoord(Vector3f *normal, float *targetU, float *targetV){   float normalisedX = 0;   float normalisedZ = -1;   if (((normal->x * normal->x) + (normal->z * normal->z)) > 0){      normalisedX = sqrt((normal->x * normal->x) / ((normal->x * normal->x) + (normal->z * normal->z)));      if (normal->x < 0){         normalisedX = -normalisedX;      }      normalisedZ = sqrt((normal->z * normal->z) / ((normal->x * normal->x) + (normal->z * normal->z)));      if (normal->z < 0){         normalisedZ = -normalisedZ;      }   }   if (normalisedZ == 0){      *targetU = ((normalisedX * PI) / 2);   }   else {      *targetU = atan(normalisedX / normalisedZ);      if (normalisedZ < 0){         *targetU += PI;      }      if (*targetU < 0){         *targetU += 2 * PI;      }   }   *targetU /= 2 * PI;   *targetV = (-normal->y + 1) / 2;}  


Enigma

This topic is closed to new replies.

Advertisement