Sign in to follow this  
Scottehy

Generating a Gsphere

Recommended Posts

Hey guys. I've just started one of my assignments for in my final year of university and I'm in need of some help with creating a gsphere. Part of my project I'm working on is creating some procedural planet generation. I've been looking at the way this great tutorial shows a simple way to make Spherical Landscapes - http://freespace.virgin.net/hugo.elias/models/m_landsp.htm In a paragraph he mentions that gspheres are a much better better choice for this problem. I've created what I think is called an lsphere with the help of some tutorials, but I've no clue on how to even create a gsphere. I've tried looking for any example of some with no luck. Another problem that lies is I'll need to create UV and normal coordinates for the gsphere as well. Heres how I've created the current sphere I'm using which works quite well but the textures pack up at the poles and it looks a little messy.
void Sphere::BuildSimpleSphere()
{
	float phiStep = PI / m_NumStacks;

	// Don't count the poles as rings.
	int numRings = m_NumStacks - 1;

	// Compute Vertices for each stack ring.
	for( int i = 1; i <= numRings; ++i )
	{
		float phi = i * phiStep;
		
		// Vertices of ring
		float thetaStep = 2.0f * PI / m_NumSlices;
		for( int j = 0; j <= m_NumSlices; ++j )
		{
			float theta = j * thetaStep;

			// Need to possibly fix up the normal and texture co-ords because of  - (m_Radius/2) due to centering the sphere
			NiPoint3 pos;
			pos.x = (m_Radius * sinf(phi) * cosf(theta)) - (m_Radius/2);
			pos.y = (m_Radius * cosf(phi)) - (m_Radius/2);
			pos.z = (m_Radius * sinf(phi) * sinf(theta));

			NiPoint3 normal = pos;
			normal.UnitizeVector(normal);

			NiPoint2 uv;
			uv.x = theta / (2.0f * PI);
			uv.y = phi / PI;

			m_Vertices.push_back(pos);
			m_Normals.push_back(normal);
			m_UVs.push_back(uv);
		}
	}

	// Poles: note that there will be texture coordinate distortion
	m_Vertices.push_back( NiPoint3((0.0f) - (m_Radius/2), (-m_Radius) - (m_Radius/2), 0.0f) );
	m_Vertices.push_back( NiPoint3((0.0f) - (m_Radius/2), (m_Radius) - (m_Radius/2), 0.0f) );

	m_Normals.push_back( NiPoint3(0.0f, -1.0f, 0.0f) );
	m_Normals.push_back( NiPoint3(0.0f, 1.0f, 0.0f) );

	m_UVs.push_back( NiPoint2(0.0f, 1.0f) );
	m_UVs.push_back( NiPoint2(0.0f, 0.0f) );

	int northPoleIndex = (int)m_Vertices.size() - 1;
	int southPoleIndex = (int)m_Vertices.size() - 2;

	int numRingVertices = m_NumSlices + 1;

	// Compute indices for inner stacks (not connected to poles).
	for(int i = 0; i < m_NumStacks - 2; ++i)
	{
		for(int j = 0; j < m_NumSlices; ++j)
		{
			m_Indices.push_back(i * numRingVertices + j);
			m_Indices.push_back(i * numRingVertices + j + 1);
			m_Indices.push_back((i + 1) * numRingVertices + j);

			m_Indices.push_back((i + 1)  *numRingVertices + j);
			m_Indices.push_back(i * numRingVertices + j + 1);
			m_Indices.push_back((i + 1)  *numRingVertices + j + 1);
		}
	}

	// Compute indices for top stack.  The top stack was written 
	// first to the vertex buffer.
	for(int i = 0; i < m_NumSlices; ++i)
	{
		m_Indices.push_back(northPoleIndex);
		m_Indices.push_back(i  +1);
		m_Indices.push_back(i);
	}

	// Compute indices for bottom stack.  The bottom stack was written
	// last to the vertex buffer, so we need to offset to the index
	// of first vertex in the last ring.
	int baseIndex = (numRings - 1) * numRingVertices;
	for(UINT i = 0; i < m_NumSlices; ++i)
	{
		m_Indices.push_back(southPoleIndex);
		m_Indices.push_back(baseIndex + i);
		m_Indices.push_back(baseIndex + i + 1);
	}
}
If anyone can show me how to create one for my situation it would be greatly appreciated as it's driving me a little stir crazy. Thanks a heap, Scott.

Share this post


Link to post
Share on other sites
Tbh, I never heard of the terms gsphere and lsphere (just g-strings), but you can get away without slices and without the singularities at the poles relatively trivial by creating 6 rectangles (or simply: a cube), and normalizing each vertex.

This gave me awesome results once I experimented with procedural planets.


pre-edit: Just found this article which might be of interest for you (I was talking about what he calls "quadcube").

Share this post


Link to post
Share on other sites
Thanks for the reply guys. It looks like the geodesic sphere is what I'm looking for. Only problem is I'm not perfect with some 3D stuff yet. So if anyone has some tutorials on how to generate one of these it would be greatly appreciated. I always get stuck on setting up things like the indices.

The VB one seems a little funky? Thanks for that great blog on the person who is building this crazy planet generator. I'm reading that at the moment.

Share this post


Link to post
Share on other sites
Quote:
Original post by Scottehy
Thanks for the reply guys. It looks like the geodesic sphere is what I'm looking for. Only problem is I'm not perfect with some 3D stuff yet. So if anyone has some tutorials on how to generate one of these it would be greatly appreciated. I always get stuck on setting up things like the indices.

The VB one seems a little funky? Thanks for that great blog on the person who is building this crazy planet generator. I'm reading that at the moment.
Indexing is always the hardest part. I say dig yourself into it, so that if you face an other indexing nightmare again, it will be much easier to solve. But a piece of paper and a pen can do wonders.

Share this post


Link to post
Share on other sites
The usual method for a geosphere is starting off with an icosahedron, and doing recursive subdivisions on each triangles by joining the midpoint of the edges. That's the closest you can get to a perfectly trianglulated sphere (which is actually mathematically impossible).

Share this post


Link to post
Share on other sites
Thanks oliii, trouble is I'm having an issue wrapping it around my head how to generate one. The tutorials I've been looking at for them are just confusing and seem a little over complex. Would you possibly have any tutorial sites on them that aren't to bad?

Share this post


Link to post
Share on other sites
I've lost my gdnet+, so I can't access my web storage, but here goes...



#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <vector>
#include <gl/glut.h>

float width = 640;
float height = 480;

float elevation = 0;
float rotation = 0;
int level = 0;
int zoom = 5;

bool keyLeft=false;
bool keyRight=false;
bool keyUp=false;
bool keyDown=false;

void glutKeyboard(unsigned char key, int x, int y)
{
if (key == 27)
exit(0);

if(key == '-')
{
if(level > 0) level--;
}

if(key == '+' || key == '=')
{
if(level < 10) level++;
}
}

void glutKeyboardUp(unsigned char key, int x, int y)
{
}

void glutSpecial(int key, int x, int y)
{
if(key == GLUT_KEY_DOWN)
keyDown = true;

if(key == GLUT_KEY_UP)
keyUp = true;

if(key == GLUT_KEY_LEFT)
keyLeft = true;

if(key == GLUT_KEY_RIGHT)
keyRight = true;
}

void glutMouse(int button, int state, int x, int y)
{
if(state == GLUT_DOWN && button == GLUT_LEFT_BUTTON && level > 0)
level--;

if(state == GLUT_DOWN && button == GLUT_RIGHT_BUTTON && level < 6)
level++;

if(state == GLUT_DOWN && button == GLUT_MIDDLE_BUTTON)
zoom = (zoom + 1) % 10;
}

void glutSpecialUp(int key, int x, int y)
{
if(key == GLUT_KEY_DOWN)
keyDown = false;

if(key == GLUT_KEY_UP)
keyUp = false;

if(key == GLUT_KEY_LEFT)
keyLeft = false;

if(key == GLUT_KEY_RIGHT)
keyRight = false;
}

void glutPassiveMotion(int x, int y)
{
rotation += (x - width / 2) / 100.0f;
elevation += (y - height / 2) / 100.0f;
}

void glutDisplay()
{
glutWarpPointer(width / 2, height / 2);

if(keyLeft) rotation += 0.03f;
if(keyRight) rotation -= 0.03f;
if(keyDown) elevation += 0.03f;
if(keyUp) elevation -= 0.03f;

float cos_rot = cos(rotation);
float sin_rot = sin(rotation);
float cos_elv = cos(elevation);
float sin_elv = sin(elevation);

float eye[3];
float dir[3];
float side[3];
float up[3];

side[0] = -sin_rot;
side[1] = 0.0f;
side[2] = cos_rot;

dir[0] = cos_rot * cos_elv;
dir[1] = sin_elv;
dir[2] = sin_rot * cos_elv;

up[0] = (side[1] * dir[2]) - (side[2] * dir[1]);
up[1] = (side[2] * dir[0]) - (side[0] * dir[2]);
up[2] = (side[0] * dir[1]) - (side[1] * dir[0]);

float distance = 6.25f - (zoom * 0.5f);

eye[0] = dir[0] * distance;
eye[1] = dir[1] * distance;
eye[2] = dir[2] * distance;

glClearColor(0.2f, 0.2f, 0.2f, 0.2f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(75.0f, 4 / 3.0f, 0.1f, 1000.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(eye[0], eye[1], eye[2], 0, 0, 0, up[0], up[1], up[2]);
glPushMatrix();

extern void renderGeosphere(unsigned int level);
renderGeosphere(level);

glPopMatrix();
glutSwapBuffers();
}

void glutTimer(int t)
{
glutDisplay();
glutTimerFunc(t, glutTimer, (int) 500.0f / 60.0f);
}

void glutReshape(int w, int h)
{
width = w;
height = h;
glViewport( 0, 0, w, h);
}

void main(int argc, char** argv)
{
glutInit( &argc, argv );
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);

glutInitWindowSize (width, height);
glutInitWindowPosition (0, 0);
glutCreateWindow ("geosphere");

glPointSize (3.0f);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable (GL_CULL_FACE);

glutPassiveMotionFunc (glutPassiveMotion);
glutKeyboardFunc (glutKeyboard);
glutKeyboardUpFunc (glutKeyboardUp);
glutSpecialFunc (glutSpecial);
glutSpecialUpFunc (glutSpecialUp);
glutMouseFunc (glutMouse);
glutDisplayFunc (glutDisplay);
glutReshapeFunc (glutReshape);
glutTimerFunc (0, glutTimer, (int) 100.0f / 60.0f);
glutIgnoreKeyRepeat (true);

// light init
GLfloat mat_ambient[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_position[] = { 0.0, -10.0, -10.0, 1.0 };
GLfloat lm_ambient[] = { 0.3, 0.3, 0.3, 1.0 };

glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialf(GL_FRONT, GL_SHININESS, 50.0);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lm_ambient);

glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);

glutMainLoop ();
}

struct Vector
{
Vector(){}
Vector(float ix, float iy, float iz) : x(ix), y(iy), z(iz){}
Vector operator+(const Vector& other) const { return Vector(x + other.x, y + other.y, z + other.z); }
Vector operator-(const Vector& other) const { return Vector(x - other.x, y - other.y, z - other.z); }
Vector operator*(const float& scalar) const { return Vector(x * scalar, y * scalar, z * scalar); }
Vector operator^(const Vector& other) const { return Vector((y * other.z) - (z * other.y), (z * other.x) - (x * other.z), (x * other.y) - (y * other.x)); }
void normalise() { float l = sqrt(x*x+y*y+z*z); x /= l; y /= l; z /= l; }
float x, y, z;
};

struct Mesh
{
Mesh()
{
m_displayList = GL_INVALID_VALUE;
}
~Mesh()
{
if(glIsList(m_displayList))
glDeleteLists(m_displayList, 1);
}

void render() const
{
if(glIsList(m_displayList))
glCallList(m_displayList);
}

bool buildDisplayList()
{
if(glIsList(m_displayList))
glDeleteLists(m_displayList, 1);

m_displayList = glGenLists(1);

if(m_displayList == GL_INVALID_VALUE)
return false;

glNewList(m_displayList, GL_COMPILE);

const Vector* vertices = &(m_vertices[0]);
unsigned int triangleCount = m_vertices.size() / 3;

glColor4f(1.0f, 0.0f, 0.0f, 0.5f);
glBegin(GL_TRIANGLES);
for(unsigned int i = 0; i < triangleCount; i ++)
{
const Vector& v0 = vertices[i * 3 + 0];
const Vector& v1 = vertices[i * 3 + 1];
const Vector& v2 = vertices[i * 3 + 2];
Vector n = (v2 - v0) ^ (v1 - v0);
n.normalise();
glNormal3f(n.x, n.y, n.z);
glVertex3f(v0.x, v0.y, v0.z);
glVertex3f(v1.x, v1.y, v1.z);
glVertex3f(v2.x, v2.y, v2.z);
}
glEnd();
glEndList();
return true;
}

GLuint m_displayList;
std::vector<Vector> m_vertices;
};

Mesh* icosahedron()
{
static const float a = (float) sqrt(2.0f/(5.0f + sqrt(5.0f)));
static const float b = (float) sqrt(2.0f/(5.0f - sqrt(5.0f)));

static Vector vertices[12] =
{
Vector(-a, 0.0f, b), Vector(a, 0.0f, b), Vector(-a, 0.0f, -b), Vector(a, 0.0f, -b),
Vector(0.0f, b, a), Vector(0.0f, b, -a), Vector(0.0f, -b, a), Vector(0.0f, -b, -a),
Vector(b, a, 0.0f), Vector(-b, a, 0.0f), Vector(b, -a, 0.0f), Vector(-b, -a, 0.0f)
};

static unsigned short triangles[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 }
};

Mesh* mesh = new Mesh;
for(int i = 0; i < 20; i ++)
{
int v0 = triangles[i][0];
int v1 = triangles[i][1];
int v2 = triangles[i][2];
mesh->m_vertices.push_back(vertices[v0]);
mesh->m_vertices.push_back(vertices[v1]);
mesh->m_vertices.push_back(vertices[v2]);
}
mesh->buildDisplayList();
return mesh;
}

Mesh* lod(const Mesh* geosphere)
{
Mesh* mesh = new Mesh;
for(unsigned int i = 0; i < geosphere->m_vertices.size() / 3; i ++)
{
const Vector& v0 = geosphere->m_vertices[i*3 + 0];
const Vector& v1 = geosphere->m_vertices[i*3 + 1];
const Vector& v2 = geosphere->m_vertices[i*3 + 2];

Vector m0 = (v0 + v1) * 0.5f;
Vector m1 = (v1 + v2) * 0.5f;
Vector m2 = (v2 + v0) * 0.5f;

m0.normalise();
m1.normalise();
m2.normalise();

mesh->m_vertices.push_back(v0);
mesh->m_vertices.push_back(m0);
mesh->m_vertices.push_back(m2);

mesh->m_vertices.push_back(m0);
mesh->m_vertices.push_back(v1);
mesh->m_vertices.push_back(m1);

mesh->m_vertices.push_back(m2);
mesh->m_vertices.push_back(m1);
mesh->m_vertices.push_back(v2);

mesh->m_vertices.push_back(m0);
mesh->m_vertices.push_back(m1);
mesh->m_vertices.push_back(m2);
}
mesh->buildDisplayList();
return mesh;
}


std::vector<const Mesh*> m_lods;

void renderGeosphere(unsigned int level)
{
while(level >= m_lods.size())
{
if(m_lods.size() == 0)
{
m_lods.push_back(icosahedron());
}
else
{
m_lods.push_back(lod(m_lods.back()));
}
}

GLfloat torus_diffuse[] = { 0.7, 0.7, 0.0, 1.0 };
GLfloat cube_diffuse[] = { 0.0, 0.7, 0.7, 1.0 };
GLfloat sphere_diffuse[] = { 0.7, 0.0, 0.7, 1.0 };
GLfloat octa_diffuse[] = { 0.7, 0.4, 0.4, 1.0 };

//glMaterialfv(GL_FRONT, GL_DIFFUSE, torus_diffuse);
glMaterialfv(GL_FRONT, GL_DIFFUSE, cube_diffuse);
//glMaterialfv(GL_FRONT, GL_DIFFUSE, sphere_diffuse);
//glMaterialfv(GL_FRONT, GL_DIFFUSE, octa_diffuse);


m_lods[level]->render();
}




in particular, this is the icosahedron generation


Mesh* icosahedron()
{
static const float a = (float) sqrt(2.0f/(5.0f + sqrt(5.0f)));
static const float b = (float) sqrt(2.0f/(5.0f - sqrt(5.0f)));

static Vector vertices[12] =
{
Vector(-a, 0.0f, b), Vector(a, 0.0f, b), Vector(-a, 0.0f, -b), Vector(a, 0.0f, -b),
Vector(0.0f, b, a), Vector(0.0f, b, -a), Vector(0.0f, -b, a), Vector(0.0f, -b, -a),
Vector(b, a, 0.0f), Vector(-b, a, 0.0f), Vector(b, -a, 0.0f), Vector(-b, -a, 0.0f)
};

static unsigned short triangles[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 }
};

Mesh* mesh = new Mesh;
for(int i = 0; i < 20; i ++)
{
int v0 = triangles[i][0];
int v1 = triangles[i][1];
int v2 = triangles[i][2];
mesh->m_vertices.push_back(vertices[v0]);
mesh->m_vertices.push_back(vertices[v1]);
mesh->m_vertices.push_back(vertices[v2]);
}
mesh->buildDisplayList();
return mesh;
}



and this is the tesselation

Mesh* lod(const Mesh* geosphere)
{
Mesh* mesh = new Mesh;
for(unsigned int i = 0; i < geosphere->m_vertices.size() / 3; i ++)
{
const Vector& v0 = geosphere->m_vertices[i*3 + 0];
const Vector& v1 = geosphere->m_vertices[i*3 + 1];
const Vector& v2 = geosphere->m_vertices[i*3 + 2];

Vector m0 = (v0 + v1) * 0.5f;
Vector m1 = (v1 + v2) * 0.5f;
Vector m2 = (v2 + v0) * 0.5f;

m0.normalise();
m1.normalise();
m2.normalise();

mesh->m_vertices.push_back(v0);
mesh->m_vertices.push_back(m0);
mesh->m_vertices.push_back(m2);

mesh->m_vertices.push_back(m0);
mesh->m_vertices.push_back(v1);
mesh->m_vertices.push_back(m1);

mesh->m_vertices.push_back(m2);
mesh->m_vertices.push_back(m1);
mesh->m_vertices.push_back(v2);

mesh->m_vertices.push_back(m0);
mesh->m_vertices.push_back(m1);
mesh->m_vertices.push_back(m2);
}
mesh->buildDisplayList();
return mesh;
}


Share this post


Link to post
Share on other sites
So basically, each level of detail is a tesselation of the previous level, where each triangle is divided into 4 smaller triangles, by taking the midpoint of the edges.

This means that each level of detail has 4 times the triangle (and vertex) count as the previous level of detail. The lowest level of detail is just the icosahedron.

It can't be any simpler! :)

Share this post


Link to post
Share on other sites
I'd have suggested oliii's solution if he hadn't beaten me to it. ;-) Let me just throw out that, although it's probably easiest to just hard-code the icosahedron vertices as oliii did, they can also be computed as the intersection of a sphere with a system of equiangular lines (which you can generate iteratively). This fact probably isn't very useful in three dimensions where it's easy to hard-code the answer, but it might be useful if you were ever in need of general-purpose n-dimensional geosphere-generation code.

Share this post


Link to post
Share on other sites
Thanks a heap oliii, I'll be having a play around with this to see how it works, Thank you so much for digging that up. Thanks for the tip as well Emergent, with a username like that would you happen to use gamebryo?

Share this post


Link to post
Share on other sites
One fun thing with geospheres is texturing. In this particular case, your texture needs to be split into triangles to be mapped on each icosahedron faces.

Share this post


Link to post
Share on other sites
Quote:
Original post by ScottehyEmergent, with a username like that would you happen to use gamebryo?


Nah, no connection; I chose the name before gamebryo came out, at a time when I was interested in emergent behaviors; the idea was that I, this being, was himself "just" the emergent behavior of a collection of cells/molecules/atoms/... (it's turtles all the way down?)

Basically, it was much pop-academic philosophical silliness. ;-)

But the name remains. Still, it beats my old one...

(I'm sure that's way more of an answer than you were looking for, so I'll call this an ending.)

Share this post


Link to post
Share on other sites
Thanks a heap oliii, you've done more than I could ask for. I didn't realize the textures had to be put into triangles like that though. Thats really weird haha. Thanks a heap :) - that article as well looks a great read. So i'm going to read through that now.

Share this post


Link to post
Share on other sites
Hmm yes, I haven;t tested the texture mapping though, so I could be wrong. Maybe a simple spherical mapping would do the trick as well.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this