# Geodesic Spheres

Hello,

I’ve just spent some time writing a few functions that create procedural geodesic “spheres” by recursively subdividng the faces of an octahedron. This gives you a sphere with lots of near-equilateral triangles in it. The most degenerate triangles are right triangles. Contrast this with the latitude-longitude globe method of making spheres, where the “fullest” triangles are right triangles, and the most degenerate triangles look like toothpicks. I had to do this because I am writing a space game, and the performance of the latitude-longitude procedural globe at maximum zoom-out was poor. (Viewed from the direction of the poles, I got weird cross artifacts and moire patterns near the edges while the globe still had some significant size on the screen.) My space game is going to allow the user to zoom out from ship-scale views, out to solar-system and interstellar scales, so good zoom-out and zoom-in performance is important.

As a result of these functions, these spheres give you equivalent smoothness with far fewer triangles. In addition, the triangles are still generated consecutively in horizontal strips, giving short vertex caches in the hardware the chance to optimize.

def bisectPoints(indexA, indexB, vertexList, vertexCache):
if indexA < indexB:
index1 = indexA
index2 = indexB
else:
index1 = indexB
index2 = indexA

key = (index1,index2)

if vertexCache.has_key(key):
return vertexCache[key]
else:
newPoint = vertexList[index1] + vertexList[index2]
newPoint.normalize()
newIndex = len(vertexList)
vertexList.append(newPoint)
vertexCache[key] = newIndex
return newIndex

def uvCoordForSphereVertex(vertex):
x = vertex.getX()
y = vertex.getY()
z = vertex.getZ()

r = math.sqrt((x*x) + (y*y))
v = 1 - (radiansLatitude / (math.pi))

u = 1 + (radiansLongitude / (2 * math.pi))

return (u,v)

def splitTriangle(triangle, vertexList, vertexCache):
index0 = triangle[0]
index1 = triangle[1]
index2 = triangle[2]

newLowerTriangleList = []

index0_1 = bisectPoints(index0, index1, vertexList, vertexCache)
index0_2 = bisectPoints(index0, index2, vertexList, vertexCache)
index1_2 = bisectPoints(index1, index2, vertexList, vertexCache)

newUpperTriangle = [index0,index0_1,index0_2]

newLowerTriangle1 = [index0_1,index1,index1_2]
newLowerTriangleList.append(newLowerTriangle1)

newLowerTriangle2 = [index1_2,index0_2,index0_1]
newLowerTriangleList.append(newLowerTriangle2)

newLowerTriangle3 = [index0_2,index1_2,index2]
newLowerTriangleList.append(newLowerTriangle3)

return [newUpperTriangle,newLowerTriangleList]

def splitTriangleCap(listOfTriangles, vertexList, vertexCache, recursionLevel):
if recursionLevel == 0:
return listOfTriangles
else:
newTriangleCap = []
newTriangleBelt = []

for triangle in listOfTriangles:
result = splitTriangle(triangle,vertexList,vertexCache)
newTriangleCap.append(result[0])
newTriangleBelt += result[1]

rval = splitTriangleCap(newTriangleCap, vertexList, vertexCache, (recursionLevel - 1))
rval += splitTriangleBelt(newTriangleBelt, vertexList, vertexCache, (recursionLevel - 1))
return rval

def splitTriangleBelt(listOfTriangles, vertexList, vertexCache, recursionLevel):
if recursionLevel == 0:
return listOfTriangles
else:
upperTriangleBelt = []
lowerTriangleBelt = []

evenFlag = 1

for triangle in listOfTriangles:
result = splitTriangle(triangle,vertexList,vertexCache)
if evenFlag:
upperTriangleBelt.append(result[0])
lowerTriangleBelt += result[1]
else:
result[1].reverse()
upperTriangleBelt += (result[1])
lowerTriangleBelt.append(result[0])
evenFlag = not evenFlag

rval = splitTriangleBelt(upperTriangleBelt, vertexList, vertexCache, (recursionLevel - 1))
rval += splitTriangleBelt(lowerTriangleBelt, vertexList, vertexCache, (recursionLevel - 1))
return rval

def makeGeodesicWireframeDome(recursionLevel, scale, colorValues, name):
vertexList = []
vertexList.append(Point3(0,0,1))
vertexList.append(Point3(-1,0,0))
vertexList.append(Point3(0,1,0))
vertexList.append(Point3(1,0,0))
vertexList.append(Point3(0,-1,0))
initialUpperCap = [[0,1,2],[0,2,3],[0,3,4],[0,4,1]]

triangleList = splitTriangleCap(initialUpperCap, vertexList, recursionLevel)

# Set up the format and vertex data  and vertex data writer objects
format = GeomVertexFormat.getV3n3c4()
vdata = GeomVertexData(name, format, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, 'vertex')
color = GeomVertexWriter(vdata, 'color')
normal = GeomVertexWriter(vdata, 'normal')

# Populate the vertex data and the coordinate index
for v in vertexList:
x = v.getX()
y = v.getY()
z = v.getZ()

# Now that we have the vertexes, populate the triangles
sphere = Geom(vdata)
for triangleData in triangleList:
triangle = GeomLinestrips(Geom.UHStatic)
triangle.closePrimitive()

gNode = GeomNode(name+'GeomNode')
return gNode

def makeGeodesicSphere(recursionLevel, scale, colorValues, name):
vertexList = []
vertexCache = {}

vertexList.append(Point3(0,0,-1))
vertexList.append(Point3(-1,0,0))
vertexList.append(Point3(0,1,0))
vertexList.append(Point3(1,0,0))
vertexList.append(Point3(0,-1,0))

skewPoint = Point3(-1,-0.000001,0)
skewPoint.normalize()
vertexList.append(skewPoint)
initialUpperCap = [[0,1,2],[0,2,3],[0,3,4],[0,4,5]]

vertexList.append(Point3(0,0,1))
initialLowerCap = [[6,5,4],[6,4,3],[6,3,2],[6,2,1]]

triangleList1 = splitTriangleCap(initialUpperCap, vertexList, vertexCache, recursionLevel)
triangleList2 = splitTriangleCap(initialLowerCap, vertexList, vertexCache, recursionLevel)
triangleList = triangleList1 + triangleList2

# Set up the format and vertex data  and vertex data writer objects
format = GeomVertexFormat.getV3n3c4t2()
#format = GeomVertexFormat.getV3n3c4()
vdata = GeomVertexData(name, format, Geom.UHStatic)
vertex = GeomVertexWriter(vdata, 'vertex')
color = GeomVertexWriter(vdata, 'color')
normal = GeomVertexWriter(vdata, 'normal')
texcoord = GeomVertexWriter(vdata, 'texcoord')

# Populate the vertex data and the coordinate index
for v in vertexList:
x = v.getX()
y = v.getY()
z = v.getZ()
u,v = uvCoordForSphereVertex(v)

# Now that we have the vertexes, populate the triangles
sphere = Geom(vdata)
triangles = GeomTriangles(Geom.UHStatic)

for triangleData in triangleList:

triangles.closePrimitive()

gNode = GeomNode(name+'GeomNode')
return gNode

This code is copyrighted by me, but you may use it provided I am credited in your source code comments.

I WANNA USE II ! Just don’t know how to… Can’t type code to save my life. Just got Panda 3D…
Hi by the way

Here’s a snippet of code where I use my routine:

sphere = makeGeodesicSphere(4, 500, [1,1,1,1], 'TestSphere')
testSphereNode = render.attachNewNode('TestSphereNode')
testSphereNode.attachNewNode(sphere)
testSphereNode.setPos(0,0,1005)

I will explain the makeGeodesicSphere call’s arguments:

4 = level of recursion.

500 = scale. This determines the radius of the sphere.

[1,1,1,1] = Color information. RGB + intensity

‘TestSphere’ = name of the Geom object you are getting back.

Also be sure that you are importing the math module, as the routines make use of trig functions.