Saturday, October 23, 2010

Project 1 - Drawing a sphere, part 3

Last night I stopped at describing the algorithm for creating one strip of the circle. The code that does that is this:

glBegin(GL_TRIANGLE_STRIP);
        float r = 1 ;
        glVertex3f ( 0 , 0 , 1 ) ;
        glVertex3f( r*sin( 30 * M_PI / 180 )*cos( 0 * M_PI / 180 ) ,
                    r*sin( 30 * M_PI / 180 )*sin( 0 * M_PI / 180 ) ,
                    r*cos( 30 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 30 * M_PI / 180 )*cos( 60 * M_PI / 180 ) ,
                    r*sin( 30 * M_PI / 180 )*sin( 60 * M_PI / 180 ) ,
                    r*cos( 30 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 60 * M_PI / 180 )*cos( 0 * M_PI / 180 ) ,
                    r*sin( 60 * M_PI / 180 )*sin( 0 * M_PI / 180 ) ,
                    r*cos( 60 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 60 * M_PI / 180 )*cos( 60 * M_PI / 180 ) ,
                    r*sin( 60 * M_PI / 180 )*sin( 60 * M_PI / 180 ) ,
                    r*cos( 60 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 90 * M_PI / 180 )*cos( 0 * M_PI / 180 ) ,
                    r*sin( 90 * M_PI / 180 )*sin( 0 * M_PI / 180 ) ,
                    r*cos( 90 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 90 * M_PI / 180 )*cos( 60 * M_PI / 180 ) ,
                    r*sin( 90 * M_PI / 180 )*sin( 60 * M_PI / 180 ) ,
                    r*cos( 90 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 120 * M_PI / 180 )*cos( 0 * M_PI / 180 ) ,
                    r*sin( 120 * M_PI / 180 )*sin( 0 * M_PI / 180 ) ,
                    r*cos( 120 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 120 * M_PI / 180 )*cos( 60 * M_PI / 180 ) ,
                    r*sin( 120 * M_PI / 180 )*sin( 60 * M_PI / 180 ) ,
                    r*cos( 120 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 150 * M_PI / 180 )*cos( 0 * M_PI / 180 ) ,
                    r*sin( 150 * M_PI / 180 )*sin( 0 * M_PI / 180 ) ,
                    r*cos( 150 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 150 * M_PI / 180 )*cos( 60 * M_PI / 180 ) ,
                    r*sin( 150 * M_PI / 180 )*sin( 60 * M_PI / 180 ) ,
                    r*cos( 150 * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 180 * M_PI / 180 )*cos( 0 * M_PI / 180 ) ,
                    r*sin( 180 * M_PI / 180 )*sin( 0 * M_PI / 180 ) ,
                    r*cos( 180 * M_PI / 180 ) ) ;
    glEnd();

This is unwieldy. And I wouldn't look forwards to repeating it 5 more times to complete the sphere. However, we can get around that with a loop structure. We have two kind of loops, for and while. We have two values that vary in this example, the degrees for the first trigonometry call in each parameter, and the degrees on the second one. We will call these theta and phi.

Theta increases from 0 to 180, while phi oscillates between 0 and 60. When we try to build the whole circle, however, theta will grow to 180, then back to 0, and so on until it completes a strip with phi oscillating between 300 and 360. So we need one part of the loop where theta increases, and another where theta decreases. We also need a way to describe by how much.

So it would seem that a first way to approach it could be like this:

float r = 1 ;
for ( int phii = 0 ; phii <= 6 ; ++phii )
{
    glVertex3f( 0 , 0 , r ) ;
    for ( int thetai = 1 ; thetai <= 5 ; ++thetai )
    {
        glVertex3f( r*sin( 30 * thetai * M_PI / 180 )*cos( 60 * phii * M_PI / 180 ) ,
                          r*sin( 30 * thetai * M_PI / 180 )*sin( 60 * phii * M_PI / 180 ) ,
                          r*cos( 30 * thetai * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 30 * thetai * M_PI / 180 )*cos( 60 * (phii+1) * M_PI / 180 ) ,
                          r*sin( 30 * thetai * M_PI / 180 )*sin( 60 * (phii+1) * M_PI / 180 ) ,
                          r*cos( 30 * thetai * M_PI / 180 ) ) ;
    }
    glVertex3f( 0 , 0 , -1*r ) ;
    ++phii;

    for ( int thetai = 5 ; thetai >= 1 ; --thetai )
    {
        glVertex3f( r*sin( 30 * thetai * M_PI / 180 )*cos( 60 * phii * M_PI / 180 ) ,
                          r*sin( 30 * thetai * M_PI / 180 )*sin( 60 * phii * M_PI / 180 ) ,
                          r*cos( 30 * thetai * M_PI / 180 ) ) ;
        glVertex3f( r*sin( 30 * thetai * M_PI / 180 )*cos( 60 * (phii+1) * M_PI / 180 ) ,
                          r*sin( 30 * thetai * M_PI / 180 )*sin( 60 * (phii+1) * M_PI / 180 ) ,
                          r*cos( 30 * thetai * M_PI / 180 ) ) ;
    }

 Holy crap it works! Lookit:






There's still some modifications we can do to the code, though. For one, as it is, while the size is variable the detail is not. It only makes spheres with the same number of faces. To change this, we can add a few variables to take the place of some fixed numbers.

Let's say we want to slice the sphere in 10 instead of the 6 slices we are drawing now, north to south. Then phii would need to go up to 10, while phi would need to grow in increments of 36 instead of 60. In fact, for a given number of slices, nslices, phii has to go up to nslices and phi has to grow in 360/nslices steps. So, we add these changes to the code and....

Voila. The number of slices is now configurable. This is what the sphere looks like with 20 slices instead of 6:





You can see that while it retains the hard edges from the few number of parallels, the parallels themselves look more round now. We're looking at it now such that the north is up and the south is down, but we can look at the north pole directly to have a better view of the change:


It actually looks pretty cool from here.

Anyway, we can now do the same for theta. For a given number of cuts, ncuts, thetai has to go up to ncuts-1, and advance by 180/ncuts degrees. The results, for 20 cuts and 20 slices:






So I now have a bit of code that, for a given level of detail (cuts and slices) and size, will provide a sphere. Pretty sweet. And a good bit of practice to get back in the swing of things. There's quite a few improvements to be made still, of course. All those calls to sin and cos could be simplified by storing the values. There are only as many z values as there are cuts, so I could compute those once, store them in an array, and then refer to them instead of calculating them each time. Similar optimizations can be done for x and y.

But for now this is good enough. All that's left is to put that inside a function and I'm golden. Next up, by next week, create a solar system! It's less ambitious than it sounds, really.

No comments:

Post a Comment