Project 6.3: The Geodesic Sphere


Project Overview

In Project 6.1, we built an icosahedron. The icosahedron, in and of itself, can be useful. It is a stable structure for use in dynamic simulations and is simply an interesting shape. Much more interesting is the fact that the icosahedron is the basis for one of the most interesting geometric constructions ”the geodesic sphere.

The geodesic sphere is made up of triangles, and only triangles. The very nature of its structure allows an artist to create a rounder sphere with fewer polygons, as well as having a profile that is cleaner compared to the regular polygonal sphere. Geodesic spheres are defined by two values, their radius and their iteration. Figure 6.24 shows a geodesic sphere of iteration 0, iteration 2, and iteration 4. Each iteration contains four times as many triangles as the previous level.

click to expand
Figure 6.24: Our goal, the geodesic sphere.

In addition, this project will introduce us to dealing with geometry components in more detail than in the grass generator, some basic vector math, and will show us how we can expand and build upon previous scripts we create.

Definition and Design

One of the popular ways to build a geodesic sphere is to derive it from one of the three triangle-based Regular Polyhedra, whether the tetrahedron, the octahedron, or the icosahedron. Although we will only be working with an icosahedron, the methodology is the same, and those interested can explore expanding their skill set by trying to build spheres based on any of the three.

Geodesic spheres are built by bisecting the edges of each triangular face, connecting those new points into a triangle, and moving the new vertices out to the radius of the sphere. This process is visualized in Figure 6.25.

click to expand
Figure 6.25: Bisecting edges to produce a circle.

We can then build a flowchart of the complete tool, as seen in Flowchart 6.4.

click to expand
Flowchart 6.4: Our procedure visualized.

As you can see, we will again be passing data to the script when we execute it; in this case, both the radius and the number of iterations we want to create.

Research and Development

The majority of our research was actually done during the original construction of our icosahedron. Now, we need only investigate how we will bisect the edges of our geometry. The easiest way is to use the command polySubdivideEdge . We then have to triangulate the faces, again a simple matter of evaluating the polyTriangulate command.

Our main challenge will be using one set of commands in a loop to add detail regardless of what level of division our geodesic sphere is currently set to. We will have to evaluate the number of faces in the sphere with each iteration and dynamically build a command based on what we find.

Our final goal is to move our vertices, both the original 12 vertices in the icosahedron and any vertices created during the subdivision of the edges when we increase the detail of our geodesic sphere. We do this with some simple vector math.

In Figure 6.26, we see a circle represented by five vertices. We will say it has an arbitrary radius of 1, as seen by the inscribed circle.

click to expand
Figure 6.26: A circle defined by five vertices.

In Figure 6.27, we see edges of that pentagon bisected, producing five new vertices, each having a radius of approximately 0.81.

click to expand
Figure 6.27: The circle defined by bisecting the edges of our five-sided circle.

To bring this out to our radius of 1.0, we do a scalar multiply on the vector values of each vertex, multiplying it by approximately 1.25 as seen in Figure 6.28.

click to expand
Figure 6.28: The circle is now more defined.

We will do this same procedure to each edge of our sphere, producing a geodesic sphere. It is a common misconception that all the triangles in a geodesic sphere are equilateral to each other. This is simply impossible , as we learned when we discussed the Regular Polyhedra earlier that the icosahedron is the most complex shape that can be constructed from equilateral triangles.

Implementation

Our first goal will be to implement the radius flag into our icosahedron. We take our finished buildIcosahedron script, and save it as buildGeosphere , with arguments for both radius and iterations. See Example 6.57.

Example 6.57: Declaration of our procedure.

 global proc buildGeosphere         (float $radius, int $iterations)     {          . . . 

To bring the functionality of the radius argument into our script, we first need to determine the radius of the icosahedron as created in our present script. We do this by finding the magnitude of the vector defined by any of the vertices of the icosahedron. A trick when working in MEL to find the magnitude of a vector is to simply assign the vector value to a float variable. MEL automatically assigns the float a value equal to the magnitude of the vector. Following the declaration and initial assignment of our vertices, we add what is shown in Example 6.58.

Example 6.58: Using variable conversion to determine the magnitude of a vector.

  // To determine the vertex positions   // at the user radius  float $currRadius = $vert_01; 

The radius of the default icosahedron is approximately 1.18 units. We could hard code the value into the script to avoid this small computation, but the value held in the variable is more precise than we could assign by hand, and the computational cost is insignificant. For this reason, we often will assign many standard values with a mathematical evaluation rather than a explicit declaration.

To determine the positions of our vertices at our target radius, we need to multiply each of the vectors defined by their present position by a scalar value. To find the value by which we need to multiply each vector, we divide the target radius by the current radius. See Example 6.59.

Example 6.59: Determining how much our target sphere differs from the original.

 float $scalar = ($radius / $currRadius); 

We could unitize the vector first, but this is an unnecessary step for such a simple calculation. We now multiply the scalar by each vector, reassigning the variable holding that vector the new value. See Example 6.60.

Example 6.60: Determining the new vertex positions.

 $vert_01 = ($vert_01 * $scalar);     $vert_02 = ($vert_02 * $scalar);     $vert_03 = ($vert_03 * $scalar);     $vert_04 = ($vert_04 * $scalar);     $vert_05 = ($vert_05 * $scalar);     $vert_06 = ($vert_06 * $scalar);     $vert_07 = ($vert_07 * $scalar);     $vert_08 = ($vert_08 * $scalar);     $vert_09 = ($vert_09 * $scalar);     $vert_10 = ($vert_10 * $scalar);     $vert_11 = ($vert_11 * $scalar);     $vert_12 = ($vert_12 * $scalar); 
On the CD  

The text for this script is found on the companion CD-ROM as /project_03/v01/buildGeosphere.mel

Essentially, we are scaling the icosahedron at the vertex level. If we now run the code, we can create icosahedrons of any size .

Next, we will tackle the more complicated process of subdividing the icosahedron into the geodesic sphere. We know we want to use our $iterations variable in the evaluation of whatever operations we want to do on our icosahedron, so we will first construct the conditional statements that will control the executions of these operations.

We will want to use a for loop, but we will also want to enclose that for loop in an if statement, so that we only execute it when a user has requested a geodesic sphere, not just an icosahedron. We place this new section at the very end of the script. See Example 6.61.

Example 6.61: Using a conditional statement based on user input.

 if ($iterations > 0)         {         for ($i = 0;$i < $iterations ;$i++)             {             }         } 

The first action we need to take on our geometry is to subdivide every edge of the geometry. To do this, we use the command polySubdivideEdge . If, in the Maya interface we take an icosahedron and subdivide the edges, we can see the command in the Script Editor as shown in Example 6.62.

Example 6.62: Subdividing the polygonal edges.

 polySubdivideEdge -ws 0 -s 0 -dv 1         -ch 0 icosohedron1.e[0:29]; 

We can then translate that into Example 6.63.

Example 6.63: Clarifying the command.

 polySubdivideEdge         -worldSpace 0         -size 0         -divisions 1         -constructionHistory off         icosohedron1.e[0:29]         ; 

That final argument is our main concern. When addressing object components, we need to explicitly name them by their component number. Because we are evaluating every edge on the object, we first need to find out how many edges the current polygonal geometry has. We use the polyEvaluate command, which returns an integer array. To use this returned value in the polySubdivideEdge , we have to integrate multiple variables by doing an inline string addition. See Example 6.64.

Example 6.64: Determining the number of edges in our object.

 if ($iterations > 0)         {         for ($i = 0;$i < $iterations ;$i++)             {  // Find how many edges are in the geometry  int $numOfComponents[] =                 `polyEvaluate                     -edge                     $united_poly[0]`;  // Subdivide those edges  polySubdivideEdge                 -worldSpace 0                 -size 0                 -divisions 1                 -constructionHistory off                 ($united_poly[0]                     + ".e[0:"                      + ($numOfComponents[0] 1)                     + "]")                 ;                 }             } 
On the CD  

The text for this script is found on the companion CD-ROM as /project_03/v02/buildGeosphere.mel.

If we now evaluate the script, we will get a result similar to Figure 6.29. In Figure 6.29, we have an icosahedron with three subdivision iterations, and the vertices made visible.

click to expand
Figure 6.29: The subdivided edges.

Next, we use the polyTriangulate in a similar fashion. We modify the polyEvaluate command to also return the number of faces. Note that the order in which we add the flags for the polyEvaluate command is very important. We must keep track of which number stored in which index of the variable $numOfComponents refers to which value. In addition, note that we use a value 1 less than that returned by polyEvaluate . This is because the components are numbered with a 0-based index, but polyEvaluate returns the actual number of components. For example, if you were to use polyEvaluate on an object with just a single face, it would return a value of 1, but that one face would obviously have an index value of 0. See Example 6.65.

Example 6.65: Adding the commands to triangulate the resultant faces.

 if ($iterations > 0)         {             for ($i = 0;$i < $iterations ;$i++)                 {  // Find how many edges and faces   // are in the geometry  int $numOfComponents[] =                      `polyEvaluate                          -edge                         -face                         $united_poly[0]`;  // Subdivide those edges  polySubdivideEdge                     -worldSpace 0                     -size 0                     -divisions 1                     -constructionHistory off                     ($united_poly[0]                         + ".e[0:"                         + ($numOfComponents[0] - 1)                         + "]")                     ;  // Triangulate the faces  polyTriangulate                     -constructionHistory off                     ($united_poly[0]                         + ".f[0:"                         + ($numOfComponents[1] - 1)                         + "]")                     ;                  }         } 
On the CD  

The text for this script is found on the companion CD-ROM as /project_03/v03/buildGeosphere.mel.

This, when executed with two iterations, gives us something resembling Figure 6.30.

click to expand
Figure 6.30: Two subdivisions. triangulated.

We can now create any level of detail icosahedron, but the object still has the appearance of being constructed of 20 faces, regardless of how many the object actually contains.

To move the vertices out to the desired radius, we combine the techniques we used in all the previous changes we made to the script. First, we count the number of vertices present in the current iteration of the loop. We use our previously declared variable $numOfComponents to hold this value. See Example 6.66.

Example 6.66: Counting the vertices in the object.

  // Find how many vertices are in the geometry  $numOfComponents =              `polyEvaluate -vertex $united_poly[0]`; 

We then use a for loop to iterate through each of the vertices. Note that we did not use our standard integer iterate, $i , because we are already inside a for loop iterating with $i . Our first task is to find the position of each vertex in our geometry. To do this, we use the command xform , one of the most useful and most used commands. The command xform , when queried for a position, returns an array of three floats. We will capture these and assign them to a vector variable. See Example 6.67.

Example 6.67: Finding the position of each vertex.

 for ($n = 0; $n < $numOfComponents[0]; $n++)             {                 float $tfa[] = `xform                                     -query                                      -worldSpace                                     -translation                                      ($united_poly[0]                                         + ".vtx["                                         + $n                                         + "]")`                                     ;                 vector $v = << $tfa[0], $tfa[1], $tfa[2] >> ; 

Next, we find the magnitude of the vector defined by the position of the vector, used to find the scalar value we need to multiply that vector by to bring that vertex out to the radius requested by the user. Unfortunately, this means using a division statement, possibly a very large number of times. It is, however ugly, unavoidable. See Example 6.68.

Example 6.68: Finding the magnitude, or radius, of the sphere at each vertex.

 float $length = $v;     float $scale = ($radius / $length); 

Finally, we again use the xform command, used with multiple inline arithmetic statements, to actually move the vertex out to the desired position. See Example 6.69.

Example 6.69: Moving the vertices to their final position.

 xform         -worldSpace          -translation         ($tfa[0] * $scale)         ($tfa[1] * $scale)         ($tfa[2] * $scale)         ($united_poly[0]             + ".vtx["             + $n             + "]") ;     } 
Note  

If you want to see this process in action, add the command refresh ; on the line after the second xform command. This forces the openGL display to refresh, letting you watch the process occur. Although this drastically slows the execution of a script, it is fun to watch and is great for showing off. Occasionally, such as when you both create and delete a constraint within a script, it is actually necessary to refresh the scene.

On the CD  

The text for this script is found on the companion CD-ROM as /project_03/v04/buildGeosphere.mel.

If now evaluated, we are given a result similar to Figure 6.31.

click to expand
Figure 6.31: A sphere, but the edges of the icosahedron are still unsmoothed.

Note that the edges of the original icosahedron are clearly discernable. We will use structures similar to those in all the previous component object evaluations. We do this outside the loop going through the iterations, but still within the if statement. See Example 6.70.

Example 6.70: Softening the edges of the sphere.

 int $numOfComponents[] = `polyEvaluate                                 -edge                                 -face                                 $united_poly[0]`;     polySoftEdge         -angle 90         -constructionHistory off         ($united_poly[0]             + ".e[0:"             + ($numOfComponents[0] - 1)             + "]")         ; 

With this last revision, our script will produce the result in Figure 6.32.

click to expand
Figure 6.32: The finished geodesic sphere.
On the CD  

The text for this script is found on the companion CD-ROM as /project_03/finished/buildGeosphere.mel.

Project Conclusion and Review

In this project, we did a couple of vital things. First, we showed how to take and expand upon previously constructed scripts. We also learned how to work with object components and to construct inline command constructions. Finally, we exploited some of the features of MEL to carry out our vector calculations, essential to doing complex manipulations of positions in Cartesian space. All these features will aid us in the coming projects.

Project Script Review

 global proc buildIcosohedron ()     {     float $goldenMean = ((sqrt(5)-1)/2) ;          vector $vert_01 = << 1, 0, $goldenMean >>;     vector $vert_02 = << $goldenMean, 1, 0 >>;     vector $vert_03 = << 1, 0, ((-1.0)*$goldenMean) >>;     vector $vert_04 = << $goldenMean, -1, 0 >>;     vector $vert_05 = << 0, ((-1.0)*$goldenMean), 1 >>;     vector $vert_06 = << 0, $goldenMean, 1 >>;     vector $vert_07 = << ((-1.0)*$goldenMean), 1, 0 >>;     vector $vert_08 = << 0, $goldenMean, -1 >>;     vector $vert_09 = << 0, ((-1.0)*$goldenMean), -1 >>;     vector $vert_10 = << ((-1.0)*$goldenMean), -1, 0 >>;     vector $vert_11 = << -1, 0, $goldenMean >>;     vector $vert_12 = << -1, 0, ((-1.0)*$goldenMean)>>;          string $surf_01[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_01.x) ($vert_01.y) ($vert_01.z)         -point ($vert_03.x) ($vert_03.y) ($vert_03.z)         -point ($vert_02.x) ($vert_02.y) ($vert_02.z)         `         ;     string $surf_02[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_01.x) ($vert_01.y) ($vert_01.z)         -point ($vert_04.x) ($vert_04.y) ($vert_04.z)         -point ($vert_03.x) ($vert_03.y) ($vert_03.z)         `         ;     string $surf_03[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_01.x) ($vert_01.y) ($vert_01.z)         -point ($vert_05.x) ($vert_05.y) ($vert_05.z)         -point ($vert_04.x) ($vert_04.y) ($vert_04.z)         `         ;     string $surf_04[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_01.x) ($vert_01.y) ($vert_01.z)         -point ($vert_06.x) ($vert_06.y) ($vert_06.z)         -point ($vert_05.x) ($vert_05.y) ($vert_05.z)         `         ;     string $surf_05[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_01.x) ($vert_01.y) ($vert_01.z)         -point ($vert_02.x) ($vert_02.y) ($vert_02.z)         -point ($vert_06.x) ($vert_06.y) ($vert_06.z)         `         ;     string $surf_06[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_02.x) ($vert_02.y) ($vert_02.z)         -point ($vert_03.x) ($vert_03.y) ($vert_03.z)         -point ($vert_08.x) ($vert_08.y) ($vert_08.z)         `         ;     string $surf_07[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_03.x) ($vert_03.y) ($vert_03.z)         -point ($vert_09.x) ($vert_09.y) ($vert_09.z)         -point ($vert_08.x) ($vert_08.y) ($vert_08.z)         `         ;     string $surf_08[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_03.x) ($vert_03.y) ($vert_03.z)         -point ($vert_04.x) ($vert_04.y) ($vert_04.z)         -point ($vert_09.x) ($vert_09.y) ($vert_09.z)         `         ;     string $surf_09[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_04.x) ($vert_04.y) ($vert_04.z)         -point ($vert_10.x) ($vert_10.y) ($vert_10.z)         -point ($vert_09.x) ($vert_09.y) ($vert_09.z)         `         ;     string $surf_10[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_04.x) ($vert_04.y) ($vert_04.z)         -point ($vert_05.x) ($vert_05.y) ($vert_05.z)         -point ($vert_10.x) ($vert_10.y) ($vert_10.z)         `         ;     string $surf_11[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_05.x) ($vert_05.y) ($vert_05.z)         -point ($vert_11.x) ($vert_11.y) ($vert_11.z)         -point ($vert_10.x) ($vert_10.y) ($vert_10.z)         `         ;     string $surf_12[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_05.x) ($vert_05.y) ($vert_05.z)         -point ($vert_06.x) ($vert_06.y) ($vert_06.z)         -point ($vert_11.x) ($vert_11.y) ($vert_11.z)         `         ;     string $surf_13[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_06.x) ($vert_06.y) ($vert_06.z)         -point ($vert_07.x) ($vert_07.y) ($vert_07.z)         -point ($vert_11.x) ($vert_11.y) ($vert_11.z)         `         ;     string $surf_14[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_06.x) ($vert_06.y) ($vert_06.z)         -point ($vert_02.x) ($vert_02.y) ($vert_02.z)         -point ($vert_07.x) ($vert_07.y) ($vert_07.z)         `         ;     string $surf_15[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_02.x) ($vert_02.y) ($vert_02.z)         -point ($vert_08.x) ($vert_08.y) ($vert_08.z)         -point ($vert_07.x) ($vert_07.y) ($vert_07.z)         `         ;     string $surf_16[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_12.x) ($vert_12.y) ($vert_12.z)         -point ($vert_07.x) ($vert_07.y) ($vert_07.z)         -point ($vert_08.x) ($vert_08.y) ($vert_08.z)         `         ;     string $surf_17[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_12.x) ($vert_12.y) ($vert_12.z)         -point ($vert_08.x) ($vert_08.y) ($vert_08.z)         -point ($vert_09.x) ($vert_09.y) ($vert_09.z)         `         ;     string $surf_18[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_12.x) ($vert_12.y) ($vert_12.z)         -point ($vert_09.x) ($vert_09.y) ($vert_09.z)         -point ($vert_10.x) ($vert_10.y) ($vert_10.z)         `         ;     string $surf_19[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_12.x) ($vert_12.y) ($vert_12.z)         -point ($vert_10.x) ($vert_10.y) ($vert_10.z)         -point ($vert_11.x) ($vert_11.y) ($vert_11.z)         `         ;     string $surf_20[] = `polyCreateFacet         -constructionHistory off         -texture 1         -subdivision 1         -point ($vert_12.x) ($vert_12.y) ($vert_12.z)         -point ($vert_11.x) ($vert_11.y) ($vert_11.z)         -point ($vert_07.x) ($vert_07.y) ($vert_07.z)         `         ;     string $united_poly[] = `polyUnite         -constructionHistory off         $surf_01         $surf_02         $surf_03         $surf_04         $surf_05         $surf_06         $surf_07         $surf_08         $surf_09         $surf_10         $surf_11         $surf_12         $surf_13         $surf_14         $surf_15         $surf_16         $surf_17         $surf_18         $surf_19         $surf_20         `         ;     polyMergeVertex         -distance 0.05         -constructionHistory off         ($united_poly[0] + ".vtx[0:59]")         ;     select -replace $united_poly[0];     if ($iterations > 0)         {         for ($i = 0;$i < $iterations ;$i++)             {  // Find how many edges and faces   // are in the geometry  int $numOfComponents[] = `polyEvaluate                                         -edge                                         -face                                         $united_poly[0]`;  // Subdivide those edges  polySubdivideEdge                 -worldSpace 0                 -size 0                 -divisions 1                 -constructionHistory off                 ($united_poly[0]                     + ".e[0:"                     + ($numOfComponents[0] - 1)                     + "]")                 ;  // Triangulate the faces  polyTriangulate                 -constructionHistory off                 ($united_poly[0]                     + ".f[0:"                     + ($numOfComponents[1] -1)                     + "]")                 ;             $numOfComponents = `polyEvaluate                                     -vertex                                     $united_poly[0]`;             for ($n = 0; $n < $numOfComponents[0]; $n++)                 {                 float $tfa[] = `xform                                     -query                                      -worldSpace                                     -translation                                      ($united_poly[0]                                         + ".vtx["                                         + $n                                         + "]") `                                     ;                 vector $v = << $tfa[0], $tfa[1], $tfa[2] >> ;                 float $length = $v;                 float $scale = ($radius / $length);                 xform                     -worldSpace                      -translation                         ($tfa[0] * $scale)                         ($tfa[1] * $scale)                         ($tfa[2] * $scale)                     ($united_poly[0]                         + ".vtx["                         + $n                         + "]")                     ;             }     }         int $numOfComponents[] = `polyEvaluate                                         -edge                                         -face                                          $united_poly[0]`;         polySoftEdge             -angle 90             -constructionHistory off             ($united_poly[0]                 + ".e[0:"                 + ($numOfComponents[0] - 1)                 + "]");     }     rename $united_poly[0] "icosohedron1";     } 



The MEL Companion
The MEL Companion: Maya Scripting for 3D Artists (Charles River Media Graphics)
ISBN: 1584502754
EAN: 2147483647
Year: 2003
Pages: 101

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net