#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <time.h>

#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <gl\glaux.h>

#define PI ((float)3.14159265358979323846)

#define WIDTH 512
#define HEIGHT 512

#define COMPUTE_SPHERE_VERTICES(rings, sections) \
    (((rings)+1)*(sections)+2)
#define COMPUTE_SPHERE_TRIANGLES(rings, sections) \
    (((rings)+1)*(sections)*2)
#define RINGS 50
#define SECTIONS 50
#define SVERTICES COMPUTE_SPHERE_VERTICES(RINGS, SECTIONS)
#define STRIANGLES COMPUTE_SPHERE_TRIANGLES(RINGS, SECTIONS)

#define RGB_COLOR(red, green, blue) \
    (((DWORD)(BYTE)(red) << 0) | \
     ((DWORD)(BYTE)(green) << 8) | \
     ((DWORD)(BYTE)(blue) << 16))
    
#define FRANDOM(x) (((float)rand() / RAND_MAX) * (x))

#define DROT 10.0f

#define NLISTS 3
    
typedef struct
{
    float fX, fY, fZ;
    float fNx, fNy, fNz;
    DWORD dwColor;
} VERTEX;

typedef struct
{
    int iV1;
    int iV2;
    int iV3;
} TRIANGLE;

typedef struct _SPHERE
{
    GLfloat fXr, fYr, fZr;
    GLfloat fDXr, fDYr, fDZr;
} SPHERE;

SPHERE sphere;

BOOL fSingle = FALSE;
BOOL fDisplayList = TRUE;

int iCurrentType = 0;
GLint dlLists[NLISTS];
char *pszListType[NLISTS] =
{
    "Vertex Array", "Direct Triangles", "Direct Strips"
};

void DrawVertexArray(void);
void DrawTriangles(void);
void DrawStrips(void);
void (*pfnDraw[NLISTS])(void) =
{
    DrawVertexArray, DrawTriangles, DrawStrips
};

VERTEX vtxSphere[SVERTICES];
TRIANGLE triSphere[STRIANGLES];

void *Malloc(size_t cb)
{
    void *pv;

    pv = malloc(cb);
    if (pv == NULL)
    {
        printf("Unable to alloc %d bytes\n", cb);
        exit(1);
    }
    return pv;
}

void GenerateSphere(float fRadius, int iRings, int iSections,
                    VERTEX *pvtx, TRIANGLE *ptri)
{
    float fTheta, fPhi;             /* Angles used to sweep around sphere */
    float fDTheta, fDPhi;           /* Angle between each section and ring */
    float fX, fY, fZ, fV, fRSinTheta;  /* Temporary variables */
    int   i, j, n, m;               /* counters */
    int   iVert, iTri;              /* Internal vertex and triangle count */

    iTri = COMPUTE_SPHERE_TRIANGLES(iRings, iSections);
    iVert = COMPUTE_SPHERE_VERTICES(iRings, iSections);

    /*
     * Generate vertices at the top and bottom points.
     */
    pvtx[0].fX = 0.0f;
    pvtx[0].fY = fRadius;
    pvtx[0].fZ = 0.0f;
    pvtx[0].fNx = 0.0f;
    pvtx[0].fNy = 1.0f;
    pvtx[0].fNz = 0.0f;
    pvtx[0].dwColor = RGB_COLOR(0, 0, 255);
    pvtx[iVert - 1].fX = 0.0f;
    pvtx[iVert - 1].fY = -fRadius;
    pvtx[iVert - 1].fZ = 0.0f;
    pvtx[iVert - 1].fNx = 0.0f;
    pvtx[iVert - 1].fNy = -1.0f;
    pvtx[iVert - 1].fNz = 0.0f;
    pvtx[iVert - 1].dwColor = RGB_COLOR(0, 255, 0);

    /*
     * Generate vertex points for rings
     */
    fDTheta = PI / (float) (iRings + 2);
    fDPhi = 2.0f * PI / (float) iSections;
    n = 1; /* vertex being generated, begins at 1 to skip top point */
    fTheta = fDTheta;

    for (i = 0; i <= iRings; i++)
    {
        fY = (float)(fRadius * cos(fTheta)); /* y is the same for each ring */
        fV = fTheta / PI; /* v is the same for each ring */
        fRSinTheta = (float)(fRadius * sin(fTheta));
        fPhi = 0.0f;
	
        for (j = 0; j < iSections; j++)
        {
            fX = (float)(fRSinTheta * sin(fPhi));
            fZ = (float)(fRSinTheta * cos(fPhi));
            pvtx[n].fX = fX;
            pvtx[n].fZ = fZ;
            pvtx[n].fY = fY;
            pvtx[n].fNx = fX / fRadius;
            pvtx[n].fNy = fY / fRadius;
            pvtx[n].fNz = fZ / fRadius;
            if (n & 1)
            {
                pvtx[n].dwColor = RGB_COLOR(0, 0, 255);
            }
            else
            {
                pvtx[n].dwColor = RGB_COLOR(0, 255, 0);
            }
            fPhi += fDPhi;
            n++;
        }
	
        fTheta += fDTheta;
    }

    /*
     * Generate triangles for top and bottom caps.
     */
    for (i = 0; i < iSections; i++)
    {
        ptri[i].iV1 = 0;
        ptri[i].iV2 = i + 1;
        ptri[i].iV3 = 1 + ((i + 1) % iSections);
        ptri[iTri - iSections + i].iV1 = iVert - 1;
        ptri[iTri - iSections + i].iV2 = iVert - 2 - i;
        ptri[iTri - iSections + i].iV3 = iVert - 2 - ((1 + i) % iSections);
    }

    /*
     * Generate triangles for the rings
     */
    m = 1; /* first vertex in current ring,begins at 1 to skip top point*/
    n = iSections; /* triangle being generated, skip the top cap */
	
    for (i = 0; i < iRings; i++)
    {
        for (j = 0; j < iSections; j++)
        {
            ptri[n].iV1 = m + j;
            ptri[n].iV2 = m + iSections + j;
            ptri[n].iV3 = m + iSections + ((j + 1) % iSections);
            ptri[n + 1].iV1 = ptri[n].iV1;
            ptri[n + 1].iV2 = ptri[n].iV3;
            ptri[n + 1].iV3 = m + ((j + 1) % iSections);
            n += 2;
        }
	
        m += iSections;
    }
}

void InitSphere(void)
{
    GenerateSphere(1.0f, RINGS, SECTIONS, vtxSphere, triSphere);
    
    glVertexPointer(3, GL_FLOAT, sizeof(VERTEX), &vtxSphere[0].fX);
    glNormalPointer(GL_FLOAT, sizeof(VERTEX), &vtxSphere[0].fNx);
    glColorPointer(3, GL_UNSIGNED_BYTE, sizeof(VERTEX), &vtxSphere[0].dwColor);
    
    srand(time(NULL));
    sphere.fXr = 0.0f;
    sphere.fYr = 0.0f;
    sphere.fZr = 0.0f;
    sphere.fDXr = DROT - FRANDOM(2 * DROT);
    sphere.fDYr = DROT - FRANDOM(2 * DROT);
    sphere.fDZr = DROT - FRANDOM(2 * DROT);
}

void UpdateSphere(void)
{
    sphere.fXr += sphere.fDXr;
    sphere.fYr += sphere.fDYr;
    sphere.fZr += sphere.fDZr;
}

void DrawVertexArray(void)
{
    glDrawElements(GL_TRIANGLES, STRIANGLES*3, GL_UNSIGNED_INT, triSphere);
}

void Vertex(int iVert)
{
    VERTEX *pvtx;

    pvtx = &vtxSphere[iVert];
    glColor3ubv((GLubyte *)&pvtx->dwColor);
    glNormal3fv(&pvtx->fNx);
    glVertex3fv(&pvtx->fX);
}

void DrawTriangles(void)
{
    int iVert, *pidx;
    
    glBegin(GL_TRIANGLES);
    pidx = (int *)&triSphere[0];
    for (iVert = 0; iVert < STRIANGLES*3; iVert++)
    {
        Vertex(*pidx++);
    }
    glEnd();
}

void DrawStrips(void)
{
    int iIdxBase;
    int iRing, iSection;

    // Triangle fans for top and bottom caps
    glBegin(GL_TRIANGLE_FAN);
    
    Vertex(0);
    iIdxBase = 1;
    for (iSection = 0; iSection <= SECTIONS; iSection++)
    {
        Vertex(iIdxBase+(iSection % SECTIONS));
    }
    
    glEnd();

    glBegin(GL_TRIANGLE_FAN);

    Vertex(SVERTICES-1);
    iIdxBase = SVERTICES-SECTIONS-1;
    for (iSection = SECTIONS; iSection >= 0 ; iSection--)
    {
        Vertex(iIdxBase+(iSection % SECTIONS));
    }

    glEnd();

    // Triangle strips for each ring
    iIdxBase = 1;
    for (iRing = 0; iRing < RINGS; iRing++)
    {
        glBegin(GL_TRIANGLE_STRIP);
        
        for (iSection = 0; iSection <= SECTIONS; iSection++)
        {
            Vertex(iIdxBase+(iSection % SECTIONS));
            Vertex(iIdxBase+(iSection % SECTIONS)+SECTIONS);
        }

        glEnd();

        iIdxBase += SECTIONS;
    }
}

void Init(void)
{
    float fv4[4];
    int iv1[1];
    int i;
    GLint dlBase;
    
    InitSphere();
    
    fv4[0] = 0.05f;
    fv4[1] = 0.05f;
    fv4[2] = 0.05f;
    fv4[3] = 1.0f;
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fv4);
    
    fv4[0] = 0.0f;
    fv4[1] = 1.0f;
    fv4[2] = 1.0f;
    fv4[3] = 0.0f;
    glLightfv(GL_LIGHT0, GL_POSITION, fv4);
    fv4[0] = 0.9f;
    fv4[1] = 0.9f;
    fv4[2] = 0.9f;
    fv4[3] = 1.0f;
    glLightfv(GL_LIGHT0, GL_DIFFUSE, fv4);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);
    
    fv4[0] = 0.6f;
    fv4[1] = 0.6f;
    fv4[2] = 0.6f;
    fv4[3] = 1.0f;
    glMaterialfv(GL_FRONT, GL_SPECULAR, fv4);
    iv1[0] = 40;
    glMaterialiv(GL_FRONT, GL_SHININESS, iv1);
    
    glEnable(GL_CULL_FACE);
    
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45, 1, .01, 15);
    gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0);
    glMatrixMode(GL_MODELVIEW);

    dlBase = glGenLists(NLISTS);
    for (i = 0; i < NLISTS; i++)
    {
        dlLists[i] = dlBase+i;
        glNewList(dlLists[i], GL_COMPILE);
        pfnDraw[i]();
        glEndList();
    }
}

DWORD dwTotalMillis = 0;
int iTotalTriangles = 0;

void ResetTotals(void)
{
    dwTotalMillis = 0;
    iTotalTriangles = 0;
}

void CALLBACK Redraw(void)
{
    DWORD dwMillis;
    char szMsg[80];
    
    dwMillis = GetTickCount();

    glClear(GL_COLOR_BUFFER_BIT);

    glLoadIdentity();
    glRotatef(sphere.fXr, 1.0f, 0.0f, 0.0f);
    glRotatef(sphere.fYr, 0.0f, 1.0f, 0.0f);
    glRotatef(sphere.fZr, 0.0f, 0.0f, 1.0f);

    if (fDisplayList)
    {
        glCallList(dlLists[iCurrentType]);
    }
    else
    {
        pfnDraw[iCurrentType]();
    }
    
    UpdateSphere();

    if (fSingle)
    {
        glFlush();
    }
    else
    {
        auxSwapBuffers();
    }
    
    dwMillis = GetTickCount()-dwMillis;

    dwTotalMillis += dwMillis;
    iTotalTriangles += STRIANGLES;

    if (dwTotalMillis > 1000)
    {
        sprintf(szMsg, "%s%s: %.3lf tri/sec",
                fDisplayList ? "Display List " : "",
                pszListType[iCurrentType],
                (double)iTotalTriangles*1000.0/dwTotalMillis);
        SetWindowText(auxGetHWND(), szMsg);

        ResetTotals();
    }
}

void CALLBACK Reshape(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);
}

void CALLBACK Keyd(void)
{
    fDisplayList = !fDisplayList;
    ResetTotals();
}

void CALLBACK KeySPACE(void)
{
    iCurrentType = (iCurrentType+1) % NLISTS;
    ResetTotals();
}
    
void _CRTAPI1 main(int argc, char **argv)
{
    GLenum eMode;
    
    while (--argc > 0)
    {
        argv++;

        if (!strcmp(*argv, "-sb"))
        {
            fSingle = TRUE;
        }
    }
    
    auxInitPosition(10, 10, WIDTH, HEIGHT);
    eMode = AUX_RGB;
    if (!fSingle)
    {
        eMode |= AUX_DOUBLE;
    }
    auxInitDisplayMode(eMode);
    auxInitWindow("Vertex Array/Direct Comparison");

    auxReshapeFunc(Reshape);
    auxIdleFunc(Redraw);

    auxKeyFunc(AUX_d, Keyd);
    auxKeyFunc(AUX_SPACE, KeySPACE);

    Init();
    auxMainLoop(Redraw);
}
