#include <math.h>
#include "GoSphere.h"

#define PI 3.14159265358979324

GoSphere::GoSphere(double radius,
                   int  slices,
                   int  stacks)
{
    this->radius = radius;
    this->slices = slices;
    this->stacks = stacks;

    solidDirty = true;
    wireDirty = true;
}

void GoSphere::renderSolid(Go *go)
{
    int i;

    if(solidDirty)
        updateSolid();
    
    go->push(Go::FLAGS);
    go->enable(Go::CULL);
    
    go->render(topSolid);
    for(i = 0; i < stacks - 2; i++) {
        go->render(midSolid[i]);
    }
    go->render(botSolid);
    
    go->pop(Go::FLAGS);
}

void GoSphere::renderWire(Go *go)
{
    int i;

    if(wireDirty)
        updateWire();
    
    for(i = 0; i < stacks - 1; i++) {
        go->render(stackWire[i]);
    }
    for(i = 0; i < slices; i++) {
        go->render(sliceWire[i]);
    }
}

void GoSphere::updateSolid(void)
{
    double  p0[3];
    double  p1[3];
    int     i, j;
    
    double stackAngle;
    double stackAngleInc = PI / stacks;
    double sliceAngle;
    double sliceAngleInc = PI / slices;
    double sliceRadius1;
    double sliceRadius2;

    //
    // Create the top triangle fan.
    //
    
    sliceAngle = 0.0;
    stackAngle = stackAngleInc;
    sliceRadius2 = radius * sin(stackAngle);
    
    topSolid = new GoTriangleFan(slices + 2, Go::NORMAL);
    topSolid->xyz(0, 0, 0, radius);
    topSolid->ijk(0, 0, 0, radius);
    
    sliceAngle = PI;
    
    for(j = 0; j <= slices; j++) {

        p0[0] = sliceRadius2 * sin(sliceAngle);
        p0[1] = sliceRadius2 * cos(sliceAngle);
        p0[2] = radius * cos(stackAngle);
        
        topSolid->xyz(j + 1, p0[0], p0[1], p0[2]);
        topSolid->ijk(j + 1, p0[0], p0[1], p0[2]);
        
        sliceAngle -= sliceAngleInc * 2.0;
    }

    //
    // Create the middle triangle strips.
    //
    
    midSolid = new GoTriangleStrip *[stacks - 2];
    
    double stackAngle1;
    double stackAngle2 = stackAngle;
    
    for(i = 1; i < stacks - 1; i++) {
        
        stackAngle1 = stackAngle2;
        stackAngle2 += stackAngleInc;
        sliceRadius1 = sliceRadius2;
        sliceRadius2 = radius * sin(stackAngle2);
        
        midSolid[i - 1] = new GoTriangleStrip(slices * 2 + 2, Go::NORMAL);
        
        sliceAngle = PI;
        
        p0[0] = sliceRadius2 * sin(sliceAngle);
        p0[1] = sliceRadius2 * cos(sliceAngle);
        p0[2] = radius * cos(stackAngle2);
        
        p1[0] = sliceRadius1 * sin(sliceAngle);
        p1[1] = sliceRadius1 * cos(sliceAngle);
        p1[2] = radius * cos(stackAngle1);
        
        midSolid[i - 1]->xyz(0, p0[0], p0[1], p0[2]);
        midSolid[i - 1]->ijk(0, p0[0], p0[1], p0[2]);
        
        midSolid[i - 1]->xyz(1, p1[0], p1[1], p1[2]);
        midSolid[i - 1]->ijk(1, p1[0], p1[1], p1[2]);
        
        for(j = 0; j < slices; j++) {

            sliceAngle += sliceAngleInc * 2.0;
            
            p0[0] = sliceRadius1 * sin(sliceAngle);
            p0[1] = sliceRadius1 * cos(sliceAngle);
            p0[2] = radius * cos(stackAngle1);
            
            p1[0] = sliceRadius2 * sin(sliceAngle);
            p1[1] = sliceRadius2 * cos(sliceAngle);
            p1[2] = radius * cos(stackAngle2);
            
            midSolid[i - 1]->xyz(j*2+2, p1[0], p1[1], p1[2]);
            midSolid[i - 1]->ijk(j*2+2, p1[0], p1[1], p1[2]);
            
            midSolid[i - 1]->xyz(j*2+3, p0[0], p0[1], p0[2]);
            midSolid[i - 1]->ijk(j*2+3, p0[0], p0[1], p0[2]);
        }
        
        stackAngle += stackAngleInc * 2.0;
    }

    //
    // Create the bottom triangle fan.
    //
    
    sliceAngle = PI;
    stackAngle = PI - stackAngleInc;
    sliceRadius1 = radius * sin(stackAngle);
    
    botSolid = new GoTriangleFan(slices + 2, Go::NORMAL);
    botSolid->xyz(0, 0, 0, -radius);
    botSolid->ijk(0, 0, 0, -radius);
    
    for(j = 0; j <= slices; j++) {
        
        p0[0] = sliceRadius1 * sin(sliceAngle);
        p0[1] = sliceRadius1 * cos(sliceAngle);
        p0[2] = radius * cos(stackAngle);
        
        botSolid->xyz(j + 1, p0[0], p0[1], p0[2]);
        botSolid->ijk(j + 1, p0[0], p0[1], p0[2]);
        
        sliceAngle += sliceAngleInc * 2.0;
    }

    solidDirty = false;
}

void GoSphere::updateWire(void)
{
    double  p[3];
    int     i, j;

    double stackAngle;
    double stackAngleInc = PI / stacks;
    double sliceAngle;
    double sliceAngleInc = PI / slices;
    double sliceRadius;

    //
    // Create the stacks.
    //

    stackWire = new GoLineLoop *[stacks - 1];
    
    stackAngle = stackAngleInc;
    
    for(i = 0; i < stacks - 1; i++) {
        
        sliceAngle = 0.0;
        sliceRadius = radius * sin(stackAngle);
        
        stackWire[i] = new GoLineLoop(slices, Go::NORMAL);
        
        for(j = 0; j < slices; j++) {
            
            p[0] = sliceRadius * sin(sliceAngle);
            p[1] = sliceRadius * cos(sliceAngle);
            p[2] = radius * cos(stackAngle);
            
            stackWire[i]->xyz(j, p[0], p[1], p[2]);
            stackWire[i]->ijk(j, p[0], p[1], p[2]);
            
            sliceAngle -= sliceAngleInc * 2.0;
        }
        
        stackAngle += stackAngleInc;
    }
    
    //
    // Create the slices.
    //
    
    sliceWire = new GoLineStrip *[slices];
    
    sliceAngle = 0.0;
    
    for(i = 0; i < slices; i++) {
        
        stackAngle = 0.0;
        sliceRadius = radius * sin(stackAngle);
        
        sliceWire[i] = new GoLineStrip(stacks + 1, Go::NORMAL);
        
        for(j = 0; j < stacks + 1; j++) {
            
            p[0] = sliceRadius * sin(sliceAngle);
            p[1] = sliceRadius * cos(sliceAngle);
            p[2] = radius * cos(stackAngle);
            
            sliceWire[i]->xyz(j, p[0], p[1], p[2]);
            sliceWire[i]->ijk(j, p[0], p[1], p[2]);
            
            stackAngle -= stackAngleInc;
            sliceRadius = radius * sin(stackAngle);
        }
        
        sliceAngle += sliceAngleInc * 2.0;
    }

    wireDirty = false;
}