/*
 * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
 * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice including the dates of first publication and
 * either this permission notice or a reference to
 * http://oss.sgi.com/projects/FreeB/
 * shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Except as contained in this notice, the name of Silicon Graphics, Inc.
 * shall not be used in advertising or otherwise to promote the sale, use or
 * other dealings in this Software without prior written authorization from
 * Silicon Graphics, Inc.
 *
 * OpenGL ES CM 1.0 port of part of GLU by Mike Gorchak <mike@malva.ua>.
 */

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

#include "glues_quad.h"

/* Make it not a power of two to avoid cache thrashing on the chip */
#define CACHE_SIZE    240

#undef  PI
#define PI            3.14159265358979323846f

struct GLUquadric
{
    GLint       normals;
    GLboolean   textureCoords;
    GLint       orientation;
    GLint       drawStyle;
    void        (APIENTRY* errorCallback)( GLint );
};

GLAPI GLUquadric* APIENTRY gluNewQuadric(void)
{
   GLUquadric *newstate;

   newstate=(GLUquadric*)malloc(sizeof(GLUquadric));
   if (newstate==NULL)
   {
      /* Can't report an error at this point... */
      return NULL;
   }

   newstate->normals=GLU_SMOOTH;
   newstate->textureCoords=GL_FALSE;
   newstate->orientation=GLU_OUTSIDE;
   newstate->drawStyle=GLU_FILL;
   newstate->errorCallback=NULL;

   return newstate;
}

GLAPI void APIENTRY gluDeleteQuadric(GLUquadric* state)
{
   if (state!=NULL)
   {
      free(state);
   }
}

static void gluQuadricError(GLUquadric* qobj, GLenum which)
{
   if (qobj->errorCallback)
   {
      qobj->errorCallback(which);
   }
}

GLAPI void APIENTRY gluQuadricCallback(GLUquadric* qobj, GLenum which, _GLUfuncptr fn)
{
   switch (which)
   {
      case GLU_ERROR:
           qobj->errorCallback=(void(APIENTRY*)(GLint))fn;
           break;
      default:
           gluQuadricError(qobj, GLU_INVALID_ENUM);
           return;
   }
}

GLAPI void APIENTRY gluQuadricNormals(GLUquadric* qobj, GLenum normals)
{
   switch (normals)
   {
      case GLU_SMOOTH:
      case GLU_FLAT:
      case GLU_NONE:
           break;
      default:
           gluQuadricError(qobj, GLU_INVALID_ENUM);
           return;
   }
   qobj->normals=normals;
}

GLAPI void APIENTRY gluQuadricTexture(GLUquadric* qobj, GLboolean textureCoords)
{
   qobj->textureCoords=textureCoords;
}

GLAPI void APIENTRY gluQuadricOrientation(GLUquadric* qobj, GLenum orientation)
{
   switch(orientation)
   {
      case GLU_OUTSIDE:
      case GLU_INSIDE:
           break;
      default:
           gluQuadricError(qobj, GLU_INVALID_ENUM);
           return;
   }

   qobj->orientation=orientation;
}

GLAPI void APIENTRY gluQuadricDrawStyle(GLUquadric* qobj, GLenum drawStyle)
{
   switch(drawStyle)
   {
      case GLU_POINT:
      case GLU_LINE:
      case GLU_FILL:
      case GLU_SILHOUETTE:
           break;
      default:
           gluQuadricError(qobj, GLU_INVALID_ENUM);
           return;
   }

   qobj->drawStyle=drawStyle;
}

GLAPI void APIENTRY gluCylinder(GLUquadric* qobj, GLfloat baseRadius,
                                GLfloat topRadius, GLfloat height,
                                GLint slices, GLint stacks)
{
   GLint i, j;
   GLfloat sinCache[CACHE_SIZE];
   GLfloat cosCache[CACHE_SIZE];
   GLfloat sinCache2[CACHE_SIZE];
   GLfloat cosCache2[CACHE_SIZE];
   GLfloat sinCache3[CACHE_SIZE];
   GLfloat cosCache3[CACHE_SIZE];
   GLfloat sintemp, costemp;
   GLfloat vertices[(CACHE_SIZE+1)*2][3];
   GLfloat texcoords[(CACHE_SIZE+1)*2][2];
   GLfloat normals[(CACHE_SIZE+1)*2][3];
   GLfloat angle;
   GLfloat zLow, zHigh;
   GLfloat length;
   GLfloat deltaRadius;
   GLfloat zNormal;
   GLfloat xyNormalRatio;
   GLfloat radiusLow, radiusHigh;
   int needCache2, needCache3;
   GLboolean texcoord_enabled;
   GLboolean normal_enabled;
   GLboolean vertex_enabled;
   GLboolean color_enabled;

   if (slices>=CACHE_SIZE)
   {
      slices=CACHE_SIZE-1;
   }

   /* Avoid vertex array overflow */
   if (stacks>=CACHE_SIZE)
   {
      stacks=CACHE_SIZE-1;
   }

   if (slices<2 || stacks<1 || baseRadius<0.0 || topRadius<0.0 || height<0.0)
   {
      gluQuadricError(qobj, GLU_INVALID_VALUE);
      return;
   }

   /* Compute length (needed for normal calculations) */
   deltaRadius=baseRadius-topRadius;
   length=(GLfloat)sqrt(deltaRadius*deltaRadius+height*height);

   if (length==0.0)
   {
      gluQuadricError(qobj, GLU_INVALID_VALUE);
      return;
   }

   /* Cache is the vertex locations cache */
   /* Cache2 is the various normals at the vertices themselves */
   /* Cache3 is the various normals for the faces */
   needCache2=needCache3=0;
   if (qobj->normals==GLU_SMOOTH)
   {
      needCache2=1;
   }

   if (qobj->normals==GLU_FLAT)
   {
      if (qobj->drawStyle!=GLU_POINT)
      {
         needCache3=1;
      }
      if (qobj->drawStyle==GLU_LINE)
      {
         needCache2=1;
      }
   }

   zNormal=deltaRadius/length;
   xyNormalRatio=height/length;

   for (i=0; i<slices; i++)
   {
      angle=2.0f*PI*i/slices;
      if (needCache2)
      {
         if (qobj->orientation==GLU_OUTSIDE)
         {
            sinCache2[i]=(GLfloat)(xyNormalRatio*sin(angle));
            cosCache2[i]=(GLfloat)(xyNormalRatio*cos(angle));
         }
         else
         {
            sinCache2[i]=(GLfloat)(-xyNormalRatio*sin(angle));
            cosCache2[i]=(GLfloat)(-xyNormalRatio*cos(angle));
         }
      }

      sinCache[i]=(GLfloat)sin(angle);
      cosCache[i]=(GLfloat)cos(angle);
   }

   if (needCache3)
   {
      for (i=0; i<slices; i++)
      {
         angle=2.0f*PI*(i-0.5f)/slices;
         if (qobj->orientation==GLU_OUTSIDE)
         {
            sinCache3[i]=(GLfloat)(xyNormalRatio*sin(angle));
            cosCache3[i]=(GLfloat)(xyNormalRatio*cos(angle));
         }
         else
         {
            sinCache3[i]=(GLfloat)(-xyNormalRatio*sin(angle));
            cosCache3[i]=(GLfloat)(-xyNormalRatio*cos(angle));
         }
      }
   }

   sinCache[slices]=sinCache[0];
   cosCache[slices]=cosCache[0];
   if (needCache2)
   {
      sinCache2[slices]=sinCache2[0];
      cosCache2[slices]=cosCache2[0];
   }
   if (needCache3)
   {
      sinCache3[slices]=sinCache3[0];
      cosCache3[slices]=cosCache3[0];
   }

   /* Store status of enabled arrays */
   texcoord_enabled=GL_FALSE; //glIsEnabled(GL_TEXTURE_COORD_ARRAY);
   normal_enabled=GL_FALSE; //glIsEnabled(GL_NORMAL_ARRAY);
   vertex_enabled=GL_FALSE; //glIsEnabled(GL_VERTEX_ARRAY);
   color_enabled=GL_FALSE; //glIsEnabled(GL_COLOR_ARRAY);

   /* Enable or disable arrays */
   glEnableClientState(GL_VERTEX_ARRAY);
   glVertexPointer(3, GL_FLOAT, 0, vertices);
   if (qobj->textureCoords)
   {
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
      glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
   }
   else
   {
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
   }
   if (qobj->normals!=GLU_NONE)
   {
      glEnableClientState(GL_NORMAL_ARRAY);
      glNormalPointer(GL_FLOAT, 0, normals);
   }
   else
   {
      glDisableClientState(GL_NORMAL_ARRAY);
   }
   glDisableClientState(GL_COLOR_ARRAY);

   switch (qobj->drawStyle)
   {
      case GLU_FILL:
           for (j=0; j<stacks; j++)
           {
              zLow=j*height/stacks;
              zHigh=(j+1)*height/stacks;
              radiusLow=baseRadius-deltaRadius*((GLfloat)j/stacks);
              radiusHigh=baseRadius-deltaRadius*((GLfloat)(j+1)/stacks);

              for (i=0; i<=slices; i++)
              {
                 switch(qobj->normals)
                 {
                    case GLU_FLAT:
                         /* per vertex normals */
                         normals[i*2][0]=sinCache3[i];
                         normals[i*2][1]=cosCache3[i];
                         normals[i*2][2]=zNormal;
                         normals[i*2+1][0]=sinCache3[i];
                         normals[i*2+1][1]=cosCache3[i];
                         normals[i*2+1][2]=zNormal;
                         break;
                    case GLU_SMOOTH:
                         /* per vertex normals */
                         normals[i*2][0]=sinCache2[i];
                         normals[i*2][1]=cosCache2[i];
                         normals[i*2][2]=zNormal;
                         normals[i*2+1][0]=sinCache2[i];
                         normals[i*2+1][1]=cosCache2[i];
                         normals[i*2+1][2]=zNormal;
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }
                 if (qobj->orientation==GLU_OUTSIDE)
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[i*2][0]=1-(GLfloat)i/slices;
                       texcoords[i*2][1]=(GLfloat)j/stacks;
                    }
                    vertices[i*2][0]=radiusLow*sinCache[i];
                    vertices[i*2][1]=radiusLow*cosCache[i];
                    vertices[i*2][2]=zLow;

                    if (qobj->textureCoords)
                    {
                       texcoords[i*2+1][0]=1-(GLfloat)i/slices;
                       texcoords[i*2+1][1]=(GLfloat)(j+1)/stacks;
                    }
                    vertices[i*2+1][0]=radiusHigh*sinCache[i];
                    vertices[i*2+1][1]=radiusHigh*cosCache[i];
                    vertices[i*2+1][2]=zHigh;
                 }
                 else
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[i*2][0]=1-(GLfloat)i/slices;
                       texcoords[i*2][1]=(GLfloat)(j+1)/ stacks;
                    }
                    vertices[i*2][0]=radiusHigh*sinCache[i];
                    vertices[i*2][1]=radiusHigh*cosCache[i];
                    vertices[i*2][2]=zHigh;
                    if (qobj->textureCoords)
                    {
                       texcoords[i*2+1][0]=1-(GLfloat)i/slices;
                       texcoords[i*2+1][1]=(GLfloat)j/stacks;
                    }
                    vertices[i*2+1][0]=radiusLow*sinCache[i];
                    vertices[i*2+1][1]=radiusLow*cosCache[i];
                    vertices[i*2+1][2]=zLow;
                 }
              }
              glDrawArrays(GL_TRIANGLE_STRIP, 0, 2*(slices+1));
           }
           break;
      case GLU_POINT:
           for (i=0; i<slices; i++)
           {
              sintemp=sinCache[i];
              costemp=cosCache[i];

              for (j=0; j<=stacks; j++)
              {
                 switch(qobj->normals)
                 {
                    case GLU_FLAT:
                    case GLU_SMOOTH:
                         normals[j][0]=sinCache2[i];
                         normals[j][1]=cosCache2[i];
                         normals[j][2]=zNormal;
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }

                 zLow=j*height/stacks;
                 radiusLow=baseRadius-deltaRadius*((GLfloat)j/stacks);

                 if (qobj->textureCoords)
                 {
                    texcoords[j][0]=1-(GLfloat)i/slices;
                    texcoords[j][1]=(GLfloat)j/stacks;
                 }
                 vertices[j][0]=radiusLow*sintemp;
                 vertices[j][1]=radiusLow*costemp;
                 vertices[j][2]=zLow;
              }
              glDrawArrays(GL_POINTS, 0, (stacks+1));
           }
           break;
      case GLU_LINE:
           for (j=1; j<stacks; j++)
           {
              zLow=j*height/stacks;
              radiusLow=baseRadius-deltaRadius*((GLfloat)j/stacks);

              for (i=0; i<=slices; i++)
              {
                 switch(qobj->normals)
                 {
                    case GLU_FLAT:
                         normals[i][0]=sinCache3[i];
                         normals[i][1]=cosCache3[i];
                         normals[i][2]=zNormal;
                         break;
                    case GLU_SMOOTH:
                         normals[i][0]=sinCache2[i];
                         normals[i][1]=cosCache2[i];
                         normals[i][2]=zNormal;
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }
                 if (qobj->textureCoords)
                 {
                    texcoords[i][0]=1-(GLfloat)i/slices;
                    texcoords[i][1]=(GLfloat)j/stacks;
                 }
                 vertices[i][0]=radiusLow*sinCache[i];
                 vertices[i][1]=radiusLow*cosCache[i];
                 vertices[i][2]=zLow;
              }
              glDrawArrays(GL_LINE_STRIP, 0, (slices+1));
           }
           /* Intentionally fall through here... */
      case GLU_SILHOUETTE:
           for (j=0; j<=stacks; j+=stacks)
           {
              zLow=j*height/stacks;
              radiusLow=baseRadius-deltaRadius*((GLfloat)j/stacks);

              for (i=0; i<=slices; i++)
              {
                 switch(qobj->normals)
                 {
                    case GLU_FLAT:
                         normals[i][0]=sinCache3[i];
                         normals[i][1]=cosCache3[i];
                         normals[i][2]=zNormal;
                         break;
                    case GLU_SMOOTH:
                         normals[i][0]=sinCache2[i];
                         normals[i][1]=cosCache2[i];
                         normals[i][2]=zNormal;
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }
                 if (qobj->textureCoords)
                 {
                    texcoords[i][0]=1-(GLfloat)i/slices;
                    texcoords[i][1]=(GLfloat)j/stacks;
                 }
                 vertices[i][0]=radiusLow*sinCache[i];
                 vertices[i][1]=radiusLow*cosCache[i];
                 vertices[i][2]=zLow;
              }
              glDrawArrays(GL_LINE_STRIP, 0, (slices+1));
           }

           for (i=0; i<slices; i++)
           {
              sintemp=sinCache[i];
              costemp=cosCache[i];

              for (j=0; j<=stacks; j++)
              {
                 switch(qobj->normals)
                 {
                    case GLU_FLAT:
                    case GLU_SMOOTH:
                         normals[j][0]=sinCache2[i];
                         normals[j][1]=cosCache2[i];
                         normals[j][2]=0.0f;
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }

                 zLow=j*height/stacks;
                 radiusLow=baseRadius-deltaRadius*((GLfloat)j/stacks);

                 if (qobj->textureCoords)
                 {
                    texcoords[j][0]=1-(GLfloat)i/slices;
                    texcoords[j][1]=(GLfloat)j/stacks;
                 }
                 vertices[j][0]=radiusLow*sintemp;
                 vertices[j][1]=radiusLow*costemp;
                 vertices[j][2]=zLow;
              }
              glDrawArrays(GL_LINE_STRIP, 0, (stacks+1));
           }
           break;
      default:
           break;
   }

   /* Disable or re-enable arrays */
   if (vertex_enabled)
   {
      /* Re-enable vertex array */
      glEnableClientState(GL_VERTEX_ARRAY);
   }
   else
   {
      glDisableClientState(GL_VERTEX_ARRAY);
   }

   if (texcoord_enabled)
   {
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
   }
   else
   {
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
   }

   if (normal_enabled)
   {
      glEnableClientState(GL_NORMAL_ARRAY);
   }
   else
   {
      glDisableClientState(GL_NORMAL_ARRAY);
   }

   if (color_enabled)
   {
      glEnableClientState(GL_COLOR_ARRAY);
   }
   else
   {
      glDisableClientState(GL_COLOR_ARRAY);
   }
}

GLAPI void APIENTRY gluDisk(GLUquadric* qobj, GLfloat innerRadius,
                            GLfloat outerRadius, GLint slices, GLint loops)
{
   gluPartialDisk(qobj, innerRadius, outerRadius, slices, loops, 0.0, 360.0);
}

GLAPI void APIENTRY gluPartialDisk(GLUquadric* qobj, GLfloat innerRadius,
                                   GLfloat outerRadius, GLint slices,
                                   GLint loops, GLfloat startAngle,
                                   GLfloat sweepAngle)
{
   GLint i, j;
   GLfloat sinCache[CACHE_SIZE];
   GLfloat cosCache[CACHE_SIZE];
   GLfloat angle;
   GLfloat sintemp, costemp;
   GLfloat vertices[(CACHE_SIZE+1)*2][3];
   GLfloat texcoords[(CACHE_SIZE+1)*2][2];
   GLfloat deltaRadius;
   GLfloat radiusLow, radiusHigh;
   GLfloat texLow = 0.0, texHigh = 0.0;
   GLfloat angleOffset;
   GLint slices2;
   GLint finish;
   GLboolean texcoord_enabled;
   GLboolean normal_enabled;
   GLboolean vertex_enabled;
   GLboolean color_enabled;

   if (slices>=CACHE_SIZE)
   {
      slices=CACHE_SIZE-1;
   }

   if (slices<2 || loops<1 || outerRadius<=0.0 ||
       innerRadius<0.0 ||innerRadius > outerRadius)
   {
      gluQuadricError(qobj, GLU_INVALID_VALUE);
      return;
   }

   if (sweepAngle<-360.0)
   {
      sweepAngle=-360.0;
   }
   if (sweepAngle>360.0)
   {
      sweepAngle=360.0;
   }

   if (sweepAngle<0)
   {
      startAngle+=sweepAngle;
      sweepAngle=-sweepAngle;
   }

   if (sweepAngle==360.0)
   {
      slices2=slices;
   }
   else
   {
      slices2=slices+1;
   }

   /* Compute length (needed for normal calculations) */
   deltaRadius=outerRadius-innerRadius;

   /* Cache is the vertex locations cache */
   angleOffset=startAngle/180.0f*PI;
   for (i=0; i<=slices; i++)
   {
      angle=angleOffset+((PI*sweepAngle)/180.0f)*i/slices;
      sinCache[i]=(GLfloat)sin(angle);
      cosCache[i]=(GLfloat)cos(angle);
   }

   if (sweepAngle==360.0)
   {
      sinCache[slices]=sinCache[0];
      cosCache[slices]=cosCache[0];
   }

   switch(qobj->normals)
   {
      case GLU_FLAT:
      case GLU_SMOOTH:
           if (qobj->orientation==GLU_OUTSIDE)
           {
              glNormal3f(0.0f, 0.0f, 1.0f);
           }
           else
           {
              glNormal3f(0.0f, 0.0f, -1.0f);
           }
           break;
      default:
      case GLU_NONE:
           break;
   }

   /* Store status of enabled arrays */
   texcoord_enabled=GL_FALSE; //glIsEnabled(GL_TEXTURE_COORD_ARRAY);
   normal_enabled=GL_FALSE; //glIsEnabled(GL_NORMAL_ARRAY);
   vertex_enabled=GL_FALSE; //glIsEnabled(GL_VERTEX_ARRAY);
   color_enabled=GL_FALSE; //glIsEnabled(GL_COLOR_ARRAY);

   /* Enable arrays */
   glEnableClientState(GL_VERTEX_ARRAY);
   glVertexPointer(3, GL_FLOAT, 0, vertices);
   if (qobj->textureCoords)
   {
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
      glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
   }
   else
   {
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
   }
   glDisableClientState(GL_NORMAL_ARRAY);
   glDisableClientState(GL_COLOR_ARRAY);

   switch (qobj->drawStyle)
   {
      case GLU_FILL:
           if (innerRadius==0.0)
           {
              finish=loops-1;

              /* Triangle strip for inner polygons */
              if (qobj->textureCoords)
              {
                 texcoords[0][0]=0.5f;
                 texcoords[0][1]=0.5f;
              }
              vertices[0][0]=0.0f;
              vertices[0][1]=0.0f;
              vertices[0][2]=0.0f;
              radiusLow=outerRadius-deltaRadius*((GLfloat)(loops-1)/loops);
              if (qobj->textureCoords)
              {
                 texLow=radiusLow/outerRadius/2;
              }

              if (qobj->orientation==GLU_OUTSIDE)
              {
                 for (i=slices; i>=0; i--)
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[slices-i+1][0]=texLow*sinCache[i]+0.5f;
                       texcoords[slices-i+1][1]=texLow*cosCache[i]+0.5f;
                    }
                    vertices[slices-i+1][0]=radiusLow*sinCache[i];
                    vertices[slices-i+1][1]=radiusLow*cosCache[i];
                    vertices[slices-i+1][2]=0.0f;
                 }
              }
              else
              {
                 for (i=0; i<=slices; i++)
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[i+1][0]=texLow*sinCache[i]+0.5f;
                       texcoords[i+1][1]=texLow*cosCache[i]+0.5f;
                    }
                    vertices[i+1][0]=radiusLow*sinCache[i];
                    vertices[i+1][1]=radiusLow*cosCache[i];
                    vertices[i+1][2]=0.0f;
                 }
              }
              glDrawArrays(GL_TRIANGLE_FAN, 0, (slices+2));
           }
           else
           {
              finish=loops;
           }

           for (j=0; j<finish; j++)
           {
              radiusLow=outerRadius-deltaRadius*((GLfloat)j/loops);
              radiusHigh=outerRadius-deltaRadius*((GLfloat)(j+1)/loops);
              if (qobj->textureCoords)
              {
                 texLow=radiusLow/outerRadius/2;
                 texHigh=radiusHigh/outerRadius/2;
              }

              for (i=0; i<=slices; i++)
              {
                 if (qobj->orientation==GLU_OUTSIDE)
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[i*2][0]=texLow*sinCache[i]+0.5f;
                       texcoords[i*2][1]=texLow*cosCache[i]+0.5f;
                    }
                    vertices[i*2][0]=radiusLow*sinCache[i];
                    vertices[i*2][1]=radiusLow*cosCache[i];
                    vertices[i*2][2]=0.0;

                    if (qobj->textureCoords)
                    {
                       texcoords[i*2+1][0]=texHigh*sinCache[i]+0.5f;
                       texcoords[i*2+1][1]=texHigh*cosCache[i]+0.5f;
                    }
                    vertices[i*2+1][0]=radiusHigh*sinCache[i];
                    vertices[i*2+1][1]=radiusHigh*cosCache[i];
                    vertices[i*2+1][2]=0.0;
                 }
                 else
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[i*2][0]=texHigh*sinCache[i]+0.5f;
                       texcoords[i*2][1]=texHigh*cosCache[i]+0.5f;
                    }
                    vertices[i*2][0]=radiusHigh*sinCache[i];
                    vertices[i*2][1]=radiusHigh*cosCache[i];
                    vertices[i*2][2]=0.0;

                    if (qobj->textureCoords)
                    {
                       texcoords[i*2+1][0]=texLow*sinCache[i]+0.5f;
                       texcoords[i*2+1][1]=texLow*cosCache[i]+0.5f;
                    }
                    vertices[i*2+1][0]=radiusLow*sinCache[i];
                    vertices[i*2+1][1]=radiusLow*cosCache[i];
                    vertices[i*2+1][2]=0.0;
                 }
              }
              glDrawArrays(GL_TRIANGLE_STRIP, 0, ((slices+1)*2));
           }
           break;
      case GLU_POINT:
           for (j=0; j<=loops; j++)
           {
              radiusLow=outerRadius-deltaRadius*((GLfloat)j/loops);

              for (i=0; i<slices2; i++)
              {
                 sintemp=sinCache[i];
                 costemp=cosCache[i];

                 if (qobj->textureCoords)
                 {
                    texLow=radiusLow/outerRadius/2;
                    texcoords[i][0]=texLow*sinCache[i]+0.5f;
                    texcoords[i][1]=texLow*cosCache[i]+0.5f;
                 }
                 vertices[i][0]=radiusLow*sintemp;
                 vertices[i][1]=radiusLow*costemp;
                 vertices[i][2]=0.0f;
              }
              glDrawArrays(GL_POINTS, 0, slices2);
           }
           break;
      case GLU_LINE:
           if (innerRadius==outerRadius)
           {
              for (i=0; i<=slices; i++)
              {
                 if (qobj->textureCoords)
                 {
                    texcoords[i][0]=sinCache[i]/2+0.5f;
                    texcoords[i][1]=cosCache[i]/2+0.5f;
                 }
                 vertices[i][0]=innerRadius*sinCache[i];
                 vertices[i][1]=innerRadius*cosCache[i];
                 vertices[i][2]=0.0f;
              }
              glDrawArrays(GL_LINE_STRIP, 0, slices+1);
              break;
           }

           for (j=0; j<=loops; j++)
           {
              radiusLow=outerRadius-deltaRadius*((GLfloat)j/loops);
              if (qobj->textureCoords)
              {
                 texLow=radiusLow/outerRadius/2;
              }

              for (i=0; i<=slices; i++)
              {
                 if (qobj->textureCoords)
                 {
                    texcoords[i][0]=texLow*sinCache[i]+0.5f;
                    texcoords[i][1]=texLow*cosCache[i]+0.5f;
                 }
                 vertices[i][0]=radiusLow*sinCache[i];
                 vertices[i][1]=radiusLow*cosCache[i];
                 vertices[i][2]=0.0f;
              }
              glDrawArrays(GL_LINE_STRIP, 0, slices+1);
           }

           for (i=0; i<slices2; i++)
           {
              sintemp=sinCache[i];
              costemp=cosCache[i];
              for (j=0; j<=loops; j++)
              {
                 radiusLow=outerRadius-deltaRadius*((GLfloat)j/loops);
                 if (qobj->textureCoords)
                 {
                    texLow=radiusLow/outerRadius/2;
                 }

                 if (qobj->textureCoords)
                 {
                    texcoords[j][0]=texLow*sinCache[i]+0.5f;
                    texcoords[j][1]=texLow*cosCache[i]+0.5f;
                 }
                 vertices[j][0]=radiusLow*sintemp;
                 vertices[j][1]=radiusLow*costemp;
                 vertices[j][2]=0.0f;
              }
              glDrawArrays(GL_LINE_STRIP, 0, loops+1);
           }
           break;
      case GLU_SILHOUETTE:
           if (sweepAngle<360.0)
           {
              for (i=0; i<=slices; i+=slices)
              {
                 sintemp=sinCache[i];
                 costemp=cosCache[i];

                 for (j=0; j<=loops; j++)
                 {
                    radiusLow=outerRadius-deltaRadius*((GLfloat)j/loops);

                    if (qobj->textureCoords)
                    {
                       texLow=radiusLow/outerRadius/2;
                       texcoords[j][0]=texLow*sinCache[i]+0.5f;
                       texcoords[j][1]=texLow*cosCache[i]+0.5f;
                    }
                    vertices[j][0]=radiusLow*sintemp;
                    vertices[j][1]=radiusLow*costemp;
                    vertices[j][2]=0.0f;
                 }
                 glDrawArrays(GL_LINE_STRIP, 0, loops+1);
              }
           }

           for (j=0; j<=loops; j+=loops)
           {
              radiusLow=outerRadius-deltaRadius*((GLfloat)j/loops);
              if (qobj->textureCoords)
              {
                 texLow=radiusLow/outerRadius/2;
              }

              for (i=0; i<=slices; i++)
              {
                 if (qobj->textureCoords)
                 {
                    texcoords[i][0]=texLow*sinCache[i]+0.5f;
                    texcoords[i][1]=texLow*cosCache[i]+0.5f;
                 }
                 vertices[i][0]=radiusLow*sinCache[i];
                 vertices[i][1]=radiusLow*cosCache[i];
                 vertices[i][2]=0.0f;
              }
              glDrawArrays(GL_LINE_STRIP, 0, slices+1);

              if (innerRadius==outerRadius)
              {
                 break;
              }
           }
           break;
      default:
           break;
   }

   /* Disable or re-enable arrays */
   if (vertex_enabled)
   {
      /* Re-enable vertex array */
      glEnableClientState(GL_VERTEX_ARRAY);
   }
   else
   {
      glDisableClientState(GL_VERTEX_ARRAY);
   }

   if (texcoord_enabled)
   {
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
   }
   else
   {
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
   }

   if (normal_enabled)
   {
      glEnableClientState(GL_NORMAL_ARRAY);
   }
   else
   {
      glDisableClientState(GL_NORMAL_ARRAY);
   }

   if (color_enabled)
   {
      glEnableClientState(GL_COLOR_ARRAY);
   }
   else
   {
      glDisableClientState(GL_COLOR_ARRAY);
   }
}

GLAPI void APIENTRY gluSphere(GLUquadric* qobj, GLfloat radius, GLint slices, GLint stacks)
{
   GLint i, j;
   GLfloat sinCache1a[CACHE_SIZE];
   GLfloat cosCache1a[CACHE_SIZE];
   GLfloat sinCache2a[CACHE_SIZE];
   GLfloat cosCache2a[CACHE_SIZE];
   GLfloat sinCache3a[CACHE_SIZE];
   GLfloat cosCache3a[CACHE_SIZE];
   GLfloat sinCache1b[CACHE_SIZE];
   GLfloat cosCache1b[CACHE_SIZE];
   GLfloat sinCache2b[CACHE_SIZE];
   GLfloat cosCache2b[CACHE_SIZE];
   GLfloat sinCache3b[CACHE_SIZE];
   GLfloat cosCache3b[CACHE_SIZE];
   GLfloat angle;
   GLfloat zLow, zHigh;
   GLfloat sintemp1 = 0.0, sintemp2 = 0.0, sintemp3 = 0.0, sintemp4 = 0.0;
   GLfloat costemp1 = 0.0, costemp2 = 0.0, costemp3 = 0.0, costemp4 = 0.0;
   GLfloat vertices[(CACHE_SIZE+1)*2][3];
   GLfloat texcoords[(CACHE_SIZE+1)*2][2];
   GLfloat normals[(CACHE_SIZE+1)*2][3];
   GLboolean needCache2, needCache3;
   GLint start, finish;
   GLboolean texcoord_enabled;
   GLboolean normal_enabled;
   GLboolean vertex_enabled;
   GLboolean color_enabled;

   if (slices>=CACHE_SIZE)
   {
      slices=CACHE_SIZE-1;
   }

   if (stacks>=CACHE_SIZE)
   {
      stacks=CACHE_SIZE-1;
   }

   if (slices<2 || stacks<1 || radius<0.0)
   {
      gluQuadricError(qobj, GLU_INVALID_VALUE);
      return;
   }

   /* Cache is the vertex locations cache */
   /* Cache2 is the various normals at the vertices themselves */
   /* Cache3 is the various normals for the faces */
   needCache2=needCache3=GL_FALSE;

   if (qobj->normals==GLU_SMOOTH)
   {
      needCache2=GL_TRUE;
   }

   if (qobj->normals==GLU_FLAT)
   {
      if (qobj->drawStyle!=GLU_POINT)
      {
         needCache3=GL_TRUE;
      }
      if (qobj->drawStyle==GLU_LINE)
      {
         needCache2=GL_TRUE;
      }
   }

   for (i=0; i<slices; i++)
   {
      angle=2.0f*PI*i/slices;
      sinCache1a[i]=(GLfloat)sin(angle);
      cosCache1a[i]=(GLfloat)cos(angle);
      if (needCache2)
      {
         sinCache2a[i] = sinCache1a[i];
         cosCache2a[i] = cosCache1a[i];
      }
   }

   for (j=0; j<=stacks; j++)
   {
      angle=PI*j/stacks;
      if (needCache2)
      {
         if (qobj->orientation==GLU_OUTSIDE)
         {
            sinCache2b[j]=(GLfloat)sin(angle);
            cosCache2b[j]=(GLfloat)cos(angle);
         }
         else
         {
            sinCache2b[j]=(GLfloat)-sin(angle);
            cosCache2b[j]=(GLfloat)-cos(angle);
         }
      }

      sinCache1b[j]=(GLfloat)(radius*sin(angle));
      cosCache1b[j]=(GLfloat)(radius*cos(angle));
   }

   /* Make sure it comes to a point */
   sinCache1b[0]=0;
   sinCache1b[stacks]=0;

   if (needCache3)
   {
      for (i=0; i<slices; i++)
      {
         angle=2.0f*PI*(i-0.5f)/slices;
         sinCache3a[i]=(GLfloat)sin(angle);
         cosCache3a[i]=(GLfloat)cos(angle);
      }
      for (j=0; j<=stacks; j++)
      {
         angle=PI*(j-0.5f)/stacks;
         if (qobj->orientation==GLU_OUTSIDE)
         {
            sinCache3b[j]=(GLfloat)sin(angle);
            cosCache3b[j]=(GLfloat)cos(angle);
         }
         else
         {
            sinCache3b[j]=(GLfloat)-sin(angle);
            cosCache3b[j]=(GLfloat)-cos(angle);
         }
      }
   }

   sinCache1a[slices]=sinCache1a[0];
   cosCache1a[slices]=cosCache1a[0];
   if (needCache2)
   {
      sinCache2a[slices]=sinCache2a[0];
      cosCache2a[slices]=cosCache2a[0];
   }
   if (needCache3)
   {
      sinCache3a[slices]=sinCache3a[0];
      cosCache3a[slices]=cosCache3a[0];
   }

   /* Store status of enabled arrays */
   texcoord_enabled=GL_FALSE; //glIsEnabled(GL_TEXTURE_COORD_ARRAY);
   normal_enabled=GL_FALSE; //glIsEnabled(GL_NORMAL_ARRAY);
   vertex_enabled=GL_FALSE; //glIsEnabled(GL_VERTEX_ARRAY);
   color_enabled=GL_FALSE; //glIsEnabled(GL_COLOR_ARRAY);

   /* Enable arrays */
   glEnableClientState(GL_VERTEX_ARRAY);
   glVertexPointer(3, GL_FLOAT, 0, vertices);
   if (qobj->textureCoords)
   {
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
      glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
   }
   else
   {
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
   }
   if (qobj->normals!=GLU_NONE)
   {
      glEnableClientState(GL_NORMAL_ARRAY);
      glNormalPointer(GL_FLOAT, 0, normals);
   }
   else
   {
      glDisableClientState(GL_NORMAL_ARRAY);
   }
   glDisableClientState(GL_COLOR_ARRAY);

   switch (qobj->drawStyle)
   {
      case GLU_FILL:
           if (!(qobj->textureCoords))
           {
              start=1;
              finish=stacks-1;

              /* Low end first (j == 0 iteration) */
              sintemp2=sinCache1b[1];
              zHigh=cosCache1b[1];

              switch(qobj->normals)
              {
                 case GLU_FLAT:
                      sintemp3=sinCache3b[1];
                      costemp3=cosCache3b[1];
                      normals[0][0]=sinCache3a[0]*sinCache3b[0];
                      normals[0][1]=cosCache3a[0]*sinCache3b[0];
                      normals[0][2]=cosCache3b[0];
                      break;
                 case GLU_SMOOTH:
                      sintemp3=sinCache2b[1];
                      costemp3=cosCache2b[1];
                      normals[0][0]=sinCache2a[0]*sinCache2b[0];
                      normals[0][1]=cosCache2a[0]*sinCache2b[0];
                      normals[0][2]=cosCache2b[0];
                      break;
                 default:
                      break;
              }

              vertices[0][0]=0.0f;
              vertices[0][1]=0.0f;
              vertices[0][2]=radius;

              if (qobj->orientation==GLU_OUTSIDE)
              {
                 for (i=slices; i>=0; i--)
                 {
                    switch(qobj->normals)
                    {
                       case GLU_SMOOTH:
                            normals[slices-i+1][0]=sinCache2a[i]*sintemp3;
                            normals[slices-i+1][1]=cosCache2a[i]*sintemp3;
                            normals[slices-i+1][2]=costemp3;
                            break;
                       case GLU_FLAT:
                            if (i!=slices)
                            {
                               normals[slices-i+1][0]=sinCache3a[i+1]*sintemp3;
                               normals[slices-i+1][1]=cosCache3a[i+1]*sintemp3;
                               normals[slices-i+1][2]=costemp3;
                            }
                            else
                            {
                               /* We must add any normal here */
                               normals[slices-i+1][0]=sinCache3a[i]*sintemp3;
                               normals[slices-i+1][1]=cosCache3a[i]*sintemp3;
                               normals[slices-i+1][2]=costemp3;
                            }
                            break;
                       case GLU_NONE:
                       default:
                            break;
                    }
                    vertices[slices-i+1][0]=sintemp2*sinCache1a[i];
                    vertices[slices-i+1][1]=sintemp2*cosCache1a[i];
                    vertices[slices-i+1][2]=zHigh;
                 }
              }
              else
              {
                 for (i=0; i<=slices; i++)
                 {
                    switch(qobj->normals)
                    {
                       case GLU_SMOOTH:
                            normals[i+1][0]=sinCache2a[i]*sintemp3;
                            normals[i+1][1]=cosCache2a[i]*sintemp3;
                            normals[i+1][2]=costemp3;
                            break;
                       case GLU_FLAT:
                            normals[i+1][0]=sinCache3a[i]*sintemp3;
                            normals[i+1][1]=cosCache3a[i]*sintemp3;
                            normals[i+1][2]=costemp3;
                            break;
                       case GLU_NONE:
                       default:
                            break;
                    }
                    vertices[i+1][0]=sintemp2*sinCache1a[i];
                    vertices[i+1][1]=sintemp2*cosCache1a[i];
                    vertices[i+1][2]=zHigh;
                 }
              }
              glDrawArrays(GL_TRIANGLE_FAN, 0, (slices+2));

              /* High end next (j==stacks-1 iteration) */
              sintemp2=sinCache1b[stacks-1];
              zHigh=cosCache1b[stacks-1];
              switch(qobj->normals)
              {
                 case GLU_FLAT:
                      sintemp3=sinCache3b[stacks];
                      costemp3=cosCache3b[stacks];
                      normals[0][0]=sinCache3a[stacks]*sinCache3b[stacks];
                      normals[0][1]=cosCache3a[stacks]*sinCache3b[stacks];
                      normals[0][2]=cosCache3b[stacks];
                      break;
                 case GLU_SMOOTH:
                      sintemp3=sinCache2b[stacks-1];
                      costemp3=cosCache2b[stacks-1];
                      normals[0][0]=sinCache2a[stacks]*sinCache2b[stacks];
                      normals[0][1]=cosCache2a[stacks]*sinCache2b[stacks];
                      normals[0][2]=cosCache2b[stacks];
                      break;
                 default:
                      break;
              }

              vertices[0][0]=0.0f;
              vertices[0][1]=0.0f;
              vertices[0][2]=-radius;

              if (qobj->orientation==GLU_OUTSIDE)
              {
                 for (i=0; i<=slices; i++)
                 {
                    switch(qobj->normals)
                    {
                       case GLU_SMOOTH:
                            normals[i+1][0]=sinCache2a[i]*sintemp3;
                            normals[i+1][1]=cosCache2a[i]*sintemp3;
                            normals[i+1][2]=costemp3;
                            break;
                       case GLU_FLAT:
                            normals[i+1][0]=sinCache3a[i]*sintemp3;
                            normals[i+1][1]=cosCache3a[i]*sintemp3;
                            normals[i+1][2]=costemp3;
                            break;
                       case GLU_NONE:
                       default:
                            break;
                    }
                    vertices[i+1][0]=sintemp2*sinCache1a[i];
                    vertices[i+1][1]=sintemp2*cosCache1a[i];
                    vertices[i+1][2]=zHigh;
                 }
              }
              else
              {
                 for (i=slices; i>=0; i--)
                 {
                    switch(qobj->normals)
                    {
                       case GLU_SMOOTH:
                            normals[slices-i+1][0]=sinCache2a[i]*sintemp3;
                            normals[slices-i+1][1]=cosCache2a[i]*sintemp3;
                            normals[slices-i+1][2]=costemp3;
                            break;
                       case GLU_FLAT:
                            if (i!=slices)
                            {
                               normals[slices-i+1][0]=sinCache3a[i+1]*sintemp3;
                               normals[slices-i+1][1]=cosCache3a[i+1]*sintemp3;
                               normals[slices-i+1][2]=costemp3;
                            }
                            else
                            {
                               normals[slices-i+1][0]=sinCache3a[i]*sintemp3;
                               normals[slices-i+1][1]=cosCache3a[i]*sintemp3;
                               normals[slices-i+1][2]=costemp3;
                            }
                            break;
                       case GLU_NONE:
                       default:
                            break;
                    }
                    vertices[slices-i+1][0]=sintemp2*sinCache1a[i];
                    vertices[slices-i+1][1]=sintemp2*cosCache1a[i];
                    vertices[slices-i+1][2]=zHigh;
                 }
              }
              glDrawArrays(GL_TRIANGLE_FAN, 0, (slices+2));
           }
           else
           {
              start=0;
              finish=stacks;
           }

           for (j=start; j<finish; j++)
           {
              zLow=cosCache1b[j];
              zHigh=cosCache1b[j+1];
              sintemp1=sinCache1b[j];
              sintemp2=sinCache1b[j+1];
              switch(qobj->normals)
              {
                 case GLU_FLAT:
                      sintemp4=sinCache3b[j+1];
                      costemp4=cosCache3b[j+1];
                      break;
                 case GLU_SMOOTH:
                      if (qobj->orientation==GLU_OUTSIDE)
                      {
                         sintemp3=sinCache2b[j+1];
                         costemp3=cosCache2b[j+1];
                         sintemp4=sinCache2b[j];
                         costemp4=cosCache2b[j];
                      }
                      else
                      {
                         sintemp3=sinCache2b[j];
                         costemp3=cosCache2b[j];
                         sintemp4=sinCache2b[j+1];
                         costemp4=cosCache2b[j+1];
                      }
                      break;
                 default:
                      break;
              }
              for (i=0; i<=slices; i++)
              {
                 switch(qobj->normals)
                 {
                    case GLU_SMOOTH:
                         normals[i*2][0]=sinCache2a[i]*sintemp3;
                         normals[i*2][1]=cosCache2a[i]*sintemp3;
                         normals[i*2][2]=costemp3;
                         break;
                    case GLU_FLAT:
                         normals[i*2][0]=sinCache3a[i]*sintemp4;
                         normals[i*2][1]=cosCache3a[i]*sintemp4;
                         normals[i*2][2]=costemp4;
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }
                 if (qobj->orientation==GLU_OUTSIDE)
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[i*2][0]=1-(GLfloat)i/slices;
                       texcoords[i*2][1]=1-(GLfloat)(j+1)/stacks;
                    }
                    vertices[i*2][0]=sintemp2*sinCache1a[i];
                    vertices[i*2][1]=sintemp2*cosCache1a[i];
                    vertices[i*2][2]=zHigh;
                 }
                 else
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[i*2][0]=1-(GLfloat)i/slices;
                       texcoords[i*2][1]=1-(GLfloat)j/stacks;
                    }
                    vertices[i*2][0]=sintemp1*sinCache1a[i];
                    vertices[i*2][1]=sintemp1*cosCache1a[i];
                    vertices[i*2][2]=zLow;
                 }
                 switch(qobj->normals)
                 {
                    case GLU_SMOOTH:
                         normals[i*2+1][0]=sinCache2a[i]*sintemp4;
                         normals[i*2+1][1]=cosCache2a[i]*sintemp4;
                         normals[i*2+1][2]=costemp4;
                         break;
                    case GLU_FLAT:
                         normals[i*2+1][0]=sinCache3a[i]*sintemp4;
                         normals[i*2+1][1]=cosCache3a[i]*sintemp4;
                         normals[i*2+1][2]=costemp4;
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }
                 if (qobj->orientation==GLU_OUTSIDE)
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[i*2+1][0]=1-(GLfloat)i/slices;
                       texcoords[i*2+1][1]=1-(GLfloat)j/stacks;
                    }
                    vertices[i*2+1][0]=sintemp1*sinCache1a[i];
                    vertices[i*2+1][1]=sintemp1*cosCache1a[i];
                    vertices[i*2+1][2]=zLow;
                 }
                 else
                 {
                    if (qobj->textureCoords)
                    {
                       texcoords[i*2+1][0]=1-(GLfloat)i/slices;
                       texcoords[i*2+1][1]=1-(GLfloat)(j+1)/stacks;
                    }
                    vertices[i*2+1][0]=sintemp2*sinCache1a[i];
                    vertices[i*2+1][1]=sintemp2*cosCache1a[i];
                    vertices[i*2+1][2]=zHigh;
                 }
              }
              glDrawArrays(GL_TRIANGLE_STRIP, 0, (slices+1)*2);
           }
           break;
      case GLU_POINT:
           for (j=0; j<=stacks; j++)
           {
              sintemp1=sinCache1b[j];
              costemp1=cosCache1b[j];
              switch(qobj->normals)
              {
                 case GLU_FLAT:
                 case GLU_SMOOTH:
                      sintemp2=sinCache2b[j];
                      costemp2=cosCache2b[j];
                      break;
                 default:
                      break;
              }

              for (i=0; i<slices; i++)
              {
                 switch(qobj->normals)
                 {
                    case GLU_FLAT:
                    case GLU_SMOOTH:
                         normals[i][0]=sinCache2a[i]*sintemp2;
                         normals[i][1]=cosCache2a[i]*sintemp2;
                         normals[i][2]=costemp2;
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }
                 zLow=j*radius/stacks;
                 if (qobj->textureCoords)
                 {
                    texcoords[i][0]=1-(GLfloat)i/slices;
                    texcoords[i][1]=1-(GLfloat)j/stacks;
                 }
                 vertices[i][0]=sintemp1*sinCache1a[i];
                 vertices[i][1]=sintemp1*cosCache1a[i];
                 vertices[i][2]=costemp1;
              }
              glDrawArrays(GL_POINTS, 0, slices);
           }
           break;
      case GLU_LINE:
      case GLU_SILHOUETTE:
           for (j=1; j<stacks; j++)
           {
              sintemp1=sinCache1b[j];
              costemp1=cosCache1b[j];
              switch(qobj->normals)
              {
                 case GLU_FLAT:
                 case GLU_SMOOTH:
                      sintemp2=sinCache2b[j];
                      costemp2=cosCache2b[j];
                      break;
                 default:
                      break;
              }

              for (i=0; i<=slices; i++)
              {
                 switch(qobj->normals)
                 {
                    case GLU_FLAT:
                         normals[i][0]=sinCache3a[i]*sintemp2;
                         normals[i][1]=cosCache3a[i]*sintemp2;
                         normals[i][2]=costemp2;
                         break;
                    case GLU_SMOOTH:
                         normals[i][0]=sinCache2a[i]*sintemp2;
                         normals[i][1]=cosCache2a[i]*sintemp2;
                         normals[i][2]=costemp2;
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }
                 if (qobj->textureCoords)
                 {
                    texcoords[i][0]=1-(GLfloat)i/slices;
                    texcoords[i][1]=1-(GLfloat)j/stacks;
                 }
                 vertices[i][0]=sintemp1*sinCache1a[i];
                 vertices[i][1]=sintemp1*cosCache1a[i];
                 vertices[i][2]=costemp1;
              }
              glDrawArrays(GL_LINE_STRIP, 0, slices+1);
           }

           for (i=0; i<slices; i++)
           {
              sintemp1=sinCache1a[i];
              costemp1=cosCache1a[i];
              switch(qobj->normals)
              {
                 case GLU_FLAT:
                 case GLU_SMOOTH:
                      sintemp2=sinCache2a[i];
                      costemp2=cosCache2a[i];
                      break;
                 default:
                      break;
              }

              for (j=0; j<=stacks; j++)
              {
                 switch(qobj->normals)
                 {
                    case GLU_FLAT:
                         normals[j][0]=sintemp2*sinCache3b[j];
                         normals[j][1]=costemp2*sinCache3b[j];
                         normals[j][2]=cosCache3b[j];
                         break;
                    case GLU_SMOOTH:
                         normals[j][0]=sintemp2*sinCache2b[j];
                         normals[j][1]=costemp2*sinCache2b[j];
                         normals[j][2]=cosCache2b[j];
                         break;
                    case GLU_NONE:
                    default:
                         break;
                 }

                 if (qobj->textureCoords)
                 {
                    texcoords[j][0]=1-(GLfloat)i/slices;
                    texcoords[j][1]=1-(GLfloat)j/stacks;
                 }
                 vertices[j][0]=sintemp1*sinCache1b[j];
                 vertices[j][1]=costemp1*sinCache1b[j];
                 vertices[j][2]=cosCache1b[j];
              }
              glDrawArrays(GL_LINE_STRIP, 0, stacks+1);
           }
           break;
      default:
           break;
    }

   /* Disable or re-enable arrays */
   if (vertex_enabled)
   {
      /* Re-enable vertex array */
      glEnableClientState(GL_VERTEX_ARRAY);
   }
   else
   {
      glDisableClientState(GL_VERTEX_ARRAY);
   }

   if (texcoord_enabled)
   {
      glEnableClientState(GL_TEXTURE_COORD_ARRAY);
   }
   else
   {
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
   }

   if (normal_enabled)
   {
      glEnableClientState(GL_NORMAL_ARRAY);
   }
   else
   {
      glDisableClientState(GL_NORMAL_ARRAY);
   }

   if (color_enabled)
   {
      glEnableClientState(GL_COLOR_ARRAY);
   }
   else
   {
      glDisableClientState(GL_COLOR_ARRAY);
   }
}

#define LARGE_CACHE_SIZE CACHE_SIZE

GLAPI void APIENTRY gluHemisphere(GLUquadric* qobj, GLboolean positive, GLfloat radius, GLint slices, GLint stacks)
{
    GLint i, j;
    GLfloat sinCache1a[LARGE_CACHE_SIZE];
    GLfloat cosCache1a[LARGE_CACHE_SIZE];
    GLfloat sinCache2a[LARGE_CACHE_SIZE];
    GLfloat cosCache2a[LARGE_CACHE_SIZE];
    GLfloat sinCache3a[LARGE_CACHE_SIZE];
    GLfloat cosCache3a[LARGE_CACHE_SIZE];
    GLfloat sinCache1b[LARGE_CACHE_SIZE];
    GLfloat cosCache1b[LARGE_CACHE_SIZE];
    GLfloat sinCache2b[LARGE_CACHE_SIZE];
    GLfloat cosCache2b[LARGE_CACHE_SIZE];
    GLfloat sinCache3b[LARGE_CACHE_SIZE];
    GLfloat cosCache3b[LARGE_CACHE_SIZE];
    GLfloat angle;
    GLfloat zLow, zHigh;
    GLfloat sintemp1 = 0.0, sintemp2 = 0.0, sintemp3 = 0.0, sintemp4 = 0.0;
    GLfloat costemp1 = 0.0, costemp2 = 0.0, costemp3 = 0.0, costemp4 = 0.0;
    GLfloat vertices[(LARGE_CACHE_SIZE+1)*2][3];
    GLfloat texcoords[(LARGE_CACHE_SIZE+1)*2][2];
    GLfloat normals[(LARGE_CACHE_SIZE+1)*2][3];
    GLboolean needCache2, needCache3;
    GLint start, finish;
    GLboolean texcoord_enabled;
    GLboolean normal_enabled;
    GLboolean vertex_enabled;
    GLboolean color_enabled;
    
    if (slices>=LARGE_CACHE_SIZE)
    {
        slices=LARGE_CACHE_SIZE-1;
    }
    
    if (stacks>=LARGE_CACHE_SIZE)
    {
        stacks=LARGE_CACHE_SIZE-1;
    }
    
    if (slices<2 || stacks<1 || radius<0.0)
    {
        gluQuadricError(qobj, GLU_INVALID_VALUE);
        return;
    }
    
    /* Cache is the vertex locations cache */
    /* Cache2 is the various normals at the vertices themselves */
    /* Cache3 is the various normals for the faces */
    needCache2=needCache3=GL_FALSE;
    
    if (qobj->normals==GLU_SMOOTH)
    {
        needCache2=GL_TRUE;
    }
    
    if (qobj->normals==GLU_FLAT)
    {
        if (qobj->drawStyle!=GLU_POINT)
        {
            needCache3=GL_TRUE;
        }
        if (qobj->drawStyle==GLU_LINE)
        {
            needCache2=GL_TRUE;
        }
    }
    
    if(positive)
    {
        for (i=0; i<=slices; i++)
        {
            angle=PI*i/slices;
            sinCache1a[i]=(GLfloat)sin(angle);
            cosCache1a[i]=(GLfloat)cos(angle);
            if (needCache2)
            {
                sinCache2a[i] = sinCache1a[i];
                cosCache2a[i] = cosCache1a[i];
            }
        }
    }
    else
    {
        for (i=0; i<=slices; i++)
        {
            angle=PI*i/slices;
            sinCache1a[i]=-(GLfloat)sin(angle);
            cosCache1a[i]=-(GLfloat)cos(angle);
            if (needCache2)
            {
                sinCache2a[i] = sinCache1a[i];
                cosCache2a[i] = cosCache1a[i];
            }
        }        
    }
    
    for (j=0; j<=stacks; j++)
    {
        angle=PI*j/stacks;
        if (needCache2)
        {
            if (qobj->orientation==GLU_OUTSIDE)
            {
                sinCache2b[j]=(GLfloat)sin(angle);
                cosCache2b[j]=(GLfloat)cos(angle);
            }
            else
            {
                sinCache2b[j]=(GLfloat)-sin(angle);
                cosCache2b[j]=(GLfloat)-cos(angle);
            }
        }
        
        sinCache1b[j]=(GLfloat)(radius*sin(angle));
        cosCache1b[j]=(GLfloat)(radius*cos(angle));
    }
    
    /* Make sure it comes to a point */
    sinCache1b[0]=0;
    sinCache1b[stacks]=0;
    
    if (needCache3)
    {
        if(positive)
        {
            for (i=0; i<=slices; i++)
            {
                angle=PI*(i-0.5f)/slices;
                sinCache3a[i]=(GLfloat)sin(angle);
                cosCache3a[i]=(GLfloat)cos(angle);
            }
        }
        else
        {
            for (i=0; i<=slices; i++)
            {
                angle=PI*(i-0.5f)/slices;
                sinCache3a[i]=-(GLfloat)sin(angle);
                cosCache3a[i]=-(GLfloat)cos(angle);
            }
        }
        for (j=0; j<=stacks; j++)
        {
            angle=PI*(j-0.5f)/stacks;
            if (qobj->orientation==GLU_OUTSIDE)
            {
                sinCache3b[j]=(GLfloat)sin(angle);
                cosCache3b[j]=(GLfloat)cos(angle);
            }
            else
            {
                sinCache3b[j]=(GLfloat)-sin(angle);
                cosCache3b[j]=(GLfloat)-cos(angle);
            }
        }
    }
    
    sinCache1a[slices]=sinCache1a[0];
    cosCache1a[slices]=cosCache1a[0];
    if (needCache2)
    {
        sinCache2a[slices]=sinCache2a[0];
        cosCache2a[slices]=cosCache2a[0];
    }
    if (needCache3)
    {
        sinCache3a[slices]=sinCache3a[0];
        cosCache3a[slices]=cosCache3a[0];
    }
    
    /* Store status of enabled arrays */
    texcoord_enabled=GL_FALSE; //glIsEnabled(GL_TEXTURE_COORD_ARRAY);
    normal_enabled=GL_FALSE; //glIsEnabled(GL_NORMAL_ARRAY);
    vertex_enabled=GL_FALSE; //glIsEnabled(GL_VERTEX_ARRAY);
    color_enabled=GL_FALSE; //glIsEnabled(GL_COLOR_ARRAY);
    
    /* Enable arrays */
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    if (qobj->textureCoords)
    {
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
    }
    else
    {
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    if (qobj->normals!=GLU_NONE)
    {
        glEnableClientState(GL_NORMAL_ARRAY);
        glNormalPointer(GL_FLOAT, 0, normals);
    }
    else
    {
        glDisableClientState(GL_NORMAL_ARRAY);
    }
    glDisableClientState(GL_COLOR_ARRAY);
    
    switch (qobj->drawStyle)
    {
        case GLU_FILL:
            if (!(qobj->textureCoords))
            {
                start=1;
                finish=stacks-1;
                
                /* Low end first (j == 0 iteration) */
                sintemp2=sinCache1b[1];
                zHigh=cosCache1b[1];
                
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                        sintemp3=sinCache3b[1];
                        costemp3=cosCache3b[1];
                        normals[0][0]=sinCache3a[0]*sinCache3b[0];
                        normals[0][1]=cosCache3a[0]*sinCache3b[0];
                        normals[0][2]=cosCache3b[0];
                        break;
                    case GLU_SMOOTH:
                        sintemp3=sinCache2b[1];
                        costemp3=cosCache2b[1];
                        normals[0][0]=sinCache2a[0]*sinCache2b[0];
                        normals[0][1]=cosCache2a[0]*sinCache2b[0];
                        normals[0][2]=cosCache2b[0];
                        break;
                    default:
                        break;
                }
                
                vertices[0][0]=0.0f;
                vertices[0][1]=0.0f;
                vertices[0][2]=radius;
                
                if (qobj->orientation==GLU_OUTSIDE)
                {
                    for (i=slices; i>=0; i--)
                    {
                        switch(qobj->normals)
                        {
                            case GLU_SMOOTH:
                                normals[slices-i+1][0]=sinCache2a[i]*sintemp3;
                                normals[slices-i+1][1]=cosCache2a[i]*sintemp3;
                                normals[slices-i+1][2]=costemp3;
                                break;
                            case GLU_FLAT:
                                if (i!=slices)
                                {
                                    normals[slices-i+1][0]=sinCache3a[i+1]*sintemp3;
                                    normals[slices-i+1][1]=cosCache3a[i+1]*sintemp3;
                                    normals[slices-i+1][2]=costemp3;
                                }
                                else
                                {
                                    /* We must add any normal here */
                                    normals[slices-i+1][0]=sinCache3a[i]*sintemp3;
                                    normals[slices-i+1][1]=cosCache3a[i]*sintemp3;
                                    normals[slices-i+1][2]=costemp3;
                                }
                                break;
                            case GLU_NONE:
                            default:
                                break;
                        }
                        vertices[slices-i+1][0]=sintemp2*sinCache1a[i];
                        vertices[slices-i+1][1]=sintemp2*cosCache1a[i];
                        vertices[slices-i+1][2]=zHigh;
                    }
                }
                else
                {
                    for (i=0; i<=slices; i++)
                    {
                        switch(qobj->normals)
                        {
                            case GLU_SMOOTH:
                                normals[i+1][0]=sinCache2a[i]*sintemp3;
                                normals[i+1][1]=cosCache2a[i]*sintemp3;
                                normals[i+1][2]=costemp3;
                                break;
                            case GLU_FLAT:
                                normals[i+1][0]=sinCache3a[i]*sintemp3;
                                normals[i+1][1]=cosCache3a[i]*sintemp3;
                                normals[i+1][2]=costemp3;
                                break;
                            case GLU_NONE:
                            default:
                                break;
                        }
                        vertices[i+1][0]=sintemp2*sinCache1a[i];
                        vertices[i+1][1]=sintemp2*cosCache1a[i];
                        vertices[i+1][2]=zHigh;
                    }
                }
                glDrawArrays(GL_TRIANGLE_FAN, 0, (slices+2));
                
                /* High end next (j==stacks-1 iteration) */
                sintemp2=sinCache1b[stacks-1];
                zHigh=cosCache1b[stacks-1];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                        sintemp3=sinCache3b[stacks];
                        costemp3=cosCache3b[stacks];
                        normals[0][0]=sinCache3a[stacks]*sinCache3b[stacks];
                        normals[0][1]=cosCache3a[stacks]*sinCache3b[stacks];
                        normals[0][2]=cosCache3b[stacks];
                        break;
                    case GLU_SMOOTH:
                        sintemp3=sinCache2b[stacks-1];
                        costemp3=cosCache2b[stacks-1];
                        normals[0][0]=sinCache2a[stacks]*sinCache2b[stacks];
                        normals[0][1]=cosCache2a[stacks]*sinCache2b[stacks];
                        normals[0][2]=cosCache2b[stacks];
                        break;
                    default:
                        break;
                }
                
                vertices[0][0]=0.0f;
                vertices[0][1]=0.0f;
                vertices[0][2]=-radius;
                
                if (qobj->orientation==GLU_OUTSIDE)
                {
                    for (i=0; i<=slices; i++)
                    {
                        switch(qobj->normals)
                        {
                            case GLU_SMOOTH:
                                normals[i+1][0]=sinCache2a[i]*sintemp3;
                                normals[i+1][1]=cosCache2a[i]*sintemp3;
                                normals[i+1][2]=costemp3;
                                break;
                            case GLU_FLAT:
                                normals[i+1][0]=sinCache3a[i]*sintemp3;
                                normals[i+1][1]=cosCache3a[i]*sintemp3;
                                normals[i+1][2]=costemp3;
                                break;
                            case GLU_NONE:
                            default:
                                break;
                        }
                        vertices[i+1][0]=sintemp2*sinCache1a[i];
                        vertices[i+1][1]=sintemp2*cosCache1a[i];
                        vertices[i+1][2]=zHigh;
                    }
                }
                else
                {
                    for (i=slices; i>=0; i--)
                    {
                        switch(qobj->normals)
                        {
                            case GLU_SMOOTH:
                                normals[slices-i+1][0]=sinCache2a[i]*sintemp3;
                                normals[slices-i+1][1]=cosCache2a[i]*sintemp3;
                                normals[slices-i+1][2]=costemp3;
                                break;
                            case GLU_FLAT:
                                if (i!=slices)
                                {
                                    normals[slices-i+1][0]=sinCache3a[i+1]*sintemp3;
                                    normals[slices-i+1][1]=cosCache3a[i+1]*sintemp3;
                                    normals[slices-i+1][2]=costemp3;
                                }
                                else
                                {
                                    normals[slices-i+1][0]=sinCache3a[i]*sintemp3;
                                    normals[slices-i+1][1]=cosCache3a[i]*sintemp3;
                                    normals[slices-i+1][2]=costemp3;
                                }
                                break;
                            case GLU_NONE:
                            default:
                                break;
                        }
                        vertices[slices-i+1][0]=sintemp2*sinCache1a[i];
                        vertices[slices-i+1][1]=sintemp2*cosCache1a[i];
                        vertices[slices-i+1][2]=zHigh;
                    }
                }
                glDrawArrays(GL_TRIANGLE_FAN, 0, (slices+2));
            }
            else
            {
                start=0;
                finish=stacks;
            }
            
            for (j=start; j<finish; j++)
            {
                zLow=cosCache1b[j];
                zHigh=cosCache1b[j+1];
                sintemp1=sinCache1b[j];
                sintemp2=sinCache1b[j+1];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                        sintemp4=sinCache3b[j+1];
                        costemp4=cosCache3b[j+1];
                        break;
                    case GLU_SMOOTH:
                        if (qobj->orientation==GLU_OUTSIDE)
                        {
                            sintemp3=sinCache2b[j+1];
                            costemp3=cosCache2b[j+1];
                            sintemp4=sinCache2b[j];
                            costemp4=cosCache2b[j];
                        }
                        else
                        {
                            sintemp3=sinCache2b[j];
                            costemp3=cosCache2b[j];
                            sintemp4=sinCache2b[j+1];
                            costemp4=cosCache2b[j+1];
                        }
                        break;
                    default:
                        break;
                }
                for (i=0; i<=slices; i++)
                {
                    switch(qobj->normals)
                    {
                        case GLU_SMOOTH:
                            normals[i*2][0]=sinCache2a[i]*sintemp3;
                            normals[i*2][1]=cosCache2a[i]*sintemp3;
                            normals[i*2][2]=costemp3;
                            break;
                        case GLU_FLAT:
                            normals[i*2][0]=sinCache3a[i]*sintemp4;
                            normals[i*2][1]=cosCache3a[i]*sintemp4;
                            normals[i*2][2]=costemp4;
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    if (qobj->orientation==GLU_OUTSIDE)
                    {
                        if (qobj->textureCoords)
                        {
                            texcoords[i*2][0]=1-(GLfloat)i/slices;
                            texcoords[i*2][1]=1-(GLfloat)(j+1)/stacks;
                        }
                        vertices[i*2][0]=sintemp2*sinCache1a[i];
                        vertices[i*2][1]=sintemp2*cosCache1a[i];
                        vertices[i*2][2]=zHigh;
                    }
                    else
                    {
                        if (qobj->textureCoords)
                        {
                            texcoords[i*2][0]=1-(GLfloat)i/slices;
                            texcoords[i*2][1]=1-(GLfloat)j/stacks;
                        }
                        vertices[i*2][0]=sintemp1*sinCache1a[i];
                        vertices[i*2][1]=sintemp1*cosCache1a[i];
                        vertices[i*2][2]=zLow;
                    }
                    switch(qobj->normals)
                    {
                        case GLU_SMOOTH:
                            normals[i*2+1][0]=sinCache2a[i]*sintemp4;
                            normals[i*2+1][1]=cosCache2a[i]*sintemp4;
                            normals[i*2+1][2]=costemp4;
                            break;
                        case GLU_FLAT:
                            normals[i*2+1][0]=sinCache3a[i]*sintemp4;
                            normals[i*2+1][1]=cosCache3a[i]*sintemp4;
                            normals[i*2+1][2]=costemp4;
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    if (qobj->orientation==GLU_OUTSIDE)
                    {
                        if (qobj->textureCoords)
                        {
                            texcoords[i*2+1][0]=1-(GLfloat)i/slices;
                            texcoords[i*2+1][1]=1-(GLfloat)j/stacks;
                        }
                        vertices[i*2+1][0]=sintemp1*sinCache1a[i];
                        vertices[i*2+1][1]=sintemp1*cosCache1a[i];
                        vertices[i*2+1][2]=zLow;
                    }
                    else
                    {
                        if (qobj->textureCoords)
                        {
                            texcoords[i*2+1][0]=1-(GLfloat)i/slices;
                            texcoords[i*2+1][1]=1-(GLfloat)(j+1)/stacks;
                        }
                        vertices[i*2+1][0]=sintemp2*sinCache1a[i];
                        vertices[i*2+1][1]=sintemp2*cosCache1a[i];
                        vertices[i*2+1][2]=zHigh;
                    }
                }
                glDrawArrays(GL_TRIANGLE_STRIP, 0, (slices+1)*2);
            }
            break;
        case GLU_POINT:
            for (j=0; j<=stacks; j++)
            {
                sintemp1=sinCache1b[j];
                costemp1=cosCache1b[j];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                    case GLU_SMOOTH:
                        sintemp2=sinCache2b[j];
                        costemp2=cosCache2b[j];
                        break;
                    default:
                        break;
                }
                
                for (i=0; i<slices; i++)
                {
                    switch(qobj->normals)
                    {
                        case GLU_FLAT:
                        case GLU_SMOOTH:
                            normals[i][0]=sinCache2a[i]*sintemp2;
                            normals[i][1]=cosCache2a[i]*sintemp2;
                            normals[i][2]=costemp2;
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    zLow=j*radius/stacks;
                    if (qobj->textureCoords)
                    {
                        texcoords[i][0]=1-(GLfloat)i/slices;
                        texcoords[i][1]=1-(GLfloat)j/stacks;
                    }
                    vertices[i][0]=sintemp1*sinCache1a[i];
                    vertices[i][1]=sintemp1*cosCache1a[i];
                    vertices[i][2]=costemp1;
                }
                glDrawArrays(GL_POINTS, 0, slices);
            }
            break;
        case GLU_LINE:
        case GLU_SILHOUETTE:
            for (j=1; j<stacks; j++)
            {
                sintemp1=sinCache1b[j];
                costemp1=cosCache1b[j];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                    case GLU_SMOOTH:
                        sintemp2=sinCache2b[j];
                        costemp2=cosCache2b[j];
                        break;
                    default:
                        break;
                }
                
                for (i=0; i<=slices; i++)
                {
                    switch(qobj->normals)
                    {
                        case GLU_FLAT:
                            normals[i][0]=sinCache3a[i]*sintemp2;
                            normals[i][1]=cosCache3a[i]*sintemp2;
                            normals[i][2]=costemp2;
                            break;
                        case GLU_SMOOTH:
                            normals[i][0]=sinCache2a[i]*sintemp2;
                            normals[i][1]=cosCache2a[i]*sintemp2;
                            normals[i][2]=costemp2;
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    if (qobj->textureCoords)
                    {
                        texcoords[i][0]=1-(GLfloat)i/slices;
                        texcoords[i][1]=1-(GLfloat)j/stacks;
                    }
                    vertices[i][0]=sintemp1*sinCache1a[i];
                    vertices[i][1]=sintemp1*cosCache1a[i];
                    vertices[i][2]=costemp1;
                }
                glDrawArrays(GL_LINE_STRIP, 0, slices+1);
            }
            
            for (i=0; i<slices; i++)
            {
                sintemp1=sinCache1a[i];
                costemp1=cosCache1a[i];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                    case GLU_SMOOTH:
                        sintemp2=sinCache2a[i];
                        costemp2=cosCache2a[i];
                        break;
                    default:
                        break;
                }
                
                for (j=0; j<=stacks; j++)
                {
                    switch(qobj->normals)
                    {
                        case GLU_FLAT:
                            normals[j][0]=sintemp2*sinCache3b[j];
                            normals[j][1]=costemp2*sinCache3b[j];
                            normals[j][2]=cosCache3b[j];
                            break;
                        case GLU_SMOOTH:
                            normals[j][0]=sintemp2*sinCache2b[j];
                            normals[j][1]=costemp2*sinCache2b[j];
                            normals[j][2]=cosCache2b[j];
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    
                    if (qobj->textureCoords)
                    {
                        texcoords[j][0]=1-(GLfloat)i/slices;
                        texcoords[j][1]=1-(GLfloat)j/stacks;
                    }
                    vertices[j][0]=sintemp1*sinCache1b[j];
                    vertices[j][1]=costemp1*sinCache1b[j];
                    vertices[j][2]=cosCache1b[j];
                }
                glDrawArrays(GL_LINE_STRIP, 0, stacks+1);
            }
            break;
        default:
            break;
    }
    
    /* Disable or re-enable arrays */
    if (vertex_enabled)
    {
        /* Re-enable vertex array */
        glEnableClientState(GL_VERTEX_ARRAY);
    }
    else
    {
        glDisableClientState(GL_VERTEX_ARRAY);
    }
    
    if (texcoord_enabled)
    {
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    else
    {
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    
    if (normal_enabled)
    {
        glEnableClientState(GL_NORMAL_ARRAY);
    }
    else
    {
        glDisableClientState(GL_NORMAL_ARRAY);
    }
    
    if (color_enabled)
    {
        glEnableClientState(GL_COLOR_ARRAY);
    }
    else
    {
        glDisableClientState(GL_COLOR_ARRAY);
    }
}

GLAPI void APIENTRY glu3DArc(GLUquadric* qobj, GLfloat angleWidth, GLfloat offsetAngle, GLboolean positive, GLfloat radius, GLint slices, GLint stacks)
{
    GLint i, j;
    GLfloat sinCache1a[LARGE_CACHE_SIZE];
    GLfloat cosCache1a[LARGE_CACHE_SIZE];
    GLfloat sinCache2a[LARGE_CACHE_SIZE];
    GLfloat cosCache2a[LARGE_CACHE_SIZE];
    GLfloat sinCache3a[LARGE_CACHE_SIZE];
    GLfloat cosCache3a[LARGE_CACHE_SIZE];
    GLfloat sinCache1b[LARGE_CACHE_SIZE];
    GLfloat cosCache1b[LARGE_CACHE_SIZE];
    GLfloat sinCache2b[LARGE_CACHE_SIZE];
    GLfloat cosCache2b[LARGE_CACHE_SIZE];
    GLfloat sinCache3b[LARGE_CACHE_SIZE];
    GLfloat cosCache3b[LARGE_CACHE_SIZE];
    GLfloat angle;
    GLfloat zLow, zHigh;
    GLfloat sintemp1 = 0.0, sintemp2 = 0.0, sintemp3 = 0.0, sintemp4 = 0.0;
    GLfloat costemp1 = 0.0, costemp2 = 0.0, costemp3 = 0.0, costemp4 = 0.0;
    GLfloat vertices[(LARGE_CACHE_SIZE+1)*2][3];
    GLfloat texcoords[(LARGE_CACHE_SIZE+1)*2][2];
    GLfloat normals[(LARGE_CACHE_SIZE+1)*2][3];
    GLboolean needCache2, needCache3;
    GLint start, finish;
    GLboolean texcoord_enabled;
    GLboolean normal_enabled;
    GLboolean vertex_enabled;
    GLboolean color_enabled;
    
    if (slices>=LARGE_CACHE_SIZE)
    {
        slices=LARGE_CACHE_SIZE-1;
    }
    
    if (stacks>=LARGE_CACHE_SIZE)
    {
        stacks=LARGE_CACHE_SIZE-1;
    }
    
    if (slices<2 || stacks<1 || radius<0.0)
    {
        gluQuadricError(qobj, GLU_INVALID_VALUE);
        return;
    }
    
    /* Cache is the vertex locations cache */
    /* Cache2 is the various normals at the vertices themselves */
    /* Cache3 is the various normals for the faces */
    needCache2=needCache3=GL_FALSE;
    
    if (qobj->normals==GLU_SMOOTH)
    {
        needCache2=GL_TRUE;
    }
    
    if (qobj->normals==GLU_FLAT)
    {
        if (qobj->drawStyle!=GLU_POINT)
        {
            needCache3=GL_TRUE;
        }
        if (qobj->drawStyle==GLU_LINE)
        {
            needCache2=GL_TRUE;
        }
    }
    
    if(positive)
    {
        for (i=0; i<=slices; i++)
        {
            angle=angleWidth*i/slices + offsetAngle;
            sinCache1a[i]=(GLfloat)sin(angle);
            cosCache1a[i]=(GLfloat)cos(angle);
            if (needCache2)
            {
                sinCache2a[i] = sinCache1a[i];
                cosCache2a[i] = cosCache1a[i];
            }
        }
    }
    else
    {
        for (i=0; i<=slices; i++)
        {
            angle=angleWidth*i/slices + offsetAngle;
            sinCache1a[i]=-(GLfloat)sin(angle);
            cosCache1a[i]=-(GLfloat)cos(angle);
            if (needCache2)
            {
                sinCache2a[i] = sinCache1a[i];
                cosCache2a[i] = cosCache1a[i];
            }
        }        
    }
    
    for (j=0; j<=stacks; j++)
    {
        angle=PI*j/stacks;
        if (needCache2)
        {
            if (qobj->orientation==GLU_OUTSIDE)
            {
                sinCache2b[j]=(GLfloat)sin(angle);
                cosCache2b[j]=(GLfloat)cos(angle);
            }
            else
            {
                sinCache2b[j]=(GLfloat)-sin(angle);
                cosCache2b[j]=(GLfloat)-cos(angle);
            }
        }
        
        sinCache1b[j]=(GLfloat)(radius*sin(angle));
        cosCache1b[j]=(GLfloat)(radius*cos(angle));
    }
    
    /* Make sure it comes to a point */
    sinCache1b[0]=0;
    sinCache1b[stacks]=0;
    
    if (needCache3)
    {
        if(positive)
        {
            for (i=0; i<=slices; i++)
            {
                angle=angleWidth*(i-0.5f)/slices + offsetAngle;
                sinCache3a[i]=(GLfloat)sin(angle);
                cosCache3a[i]=(GLfloat)cos(angle);
            }
        }
        else
        {
            for (i=0; i<=slices; i++)
            {
                angle=angleWidth*(i-0.5f)/slices + offsetAngle;
                sinCache3a[i]=-(GLfloat)sin(angle);
                cosCache3a[i]=-(GLfloat)cos(angle);
            }
        }
        for (j=0; j<=stacks; j++)
        {
            angle=PI*(j-0.5f)/stacks;
            if (qobj->orientation==GLU_OUTSIDE)
            {
                sinCache3b[j]=(GLfloat)sin(angle);
                cosCache3b[j]=(GLfloat)cos(angle);
            }
            else
            {
                sinCache3b[j]=(GLfloat)-sin(angle);
                cosCache3b[j]=(GLfloat)-cos(angle);
            }
        }
    }
    
    sinCache1a[slices]=sinCache1a[0];
    cosCache1a[slices]=cosCache1a[0];
    if (needCache2)
    {
        sinCache2a[slices]=sinCache2a[0];
        cosCache2a[slices]=cosCache2a[0];
    }
    if (needCache3)
    {
        sinCache3a[slices]=sinCache3a[0];
        cosCache3a[slices]=cosCache3a[0];
    }
    
    /* Store status of enabled arrays */
    texcoord_enabled=GL_FALSE; //glIsEnabled(GL_TEXTURE_COORD_ARRAY);
    normal_enabled=GL_FALSE; //glIsEnabled(GL_NORMAL_ARRAY);
    vertex_enabled=GL_FALSE; //glIsEnabled(GL_VERTEX_ARRAY);
    color_enabled=GL_FALSE; //glIsEnabled(GL_COLOR_ARRAY);
    
    /* Enable arrays */
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    if (qobj->textureCoords)
    {
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
    }
    else
    {
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    if (qobj->normals!=GLU_NONE)
    {
        glEnableClientState(GL_NORMAL_ARRAY);
        glNormalPointer(GL_FLOAT, 0, normals);
    }
    else
    {
        glDisableClientState(GL_NORMAL_ARRAY);
    }
    glDisableClientState(GL_COLOR_ARRAY);
    
    switch (qobj->drawStyle)
    {
        case GLU_FILL:
            if (!(qobj->textureCoords))
            {
                start=1;
                finish=stacks-1;
                
                /* Low end first (j == 0 iteration) */
                sintemp2=sinCache1b[1];
                zHigh=cosCache1b[1];
                
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                        sintemp3=sinCache3b[1];
                        costemp3=cosCache3b[1];
                        normals[0][0]=sinCache3a[0]*sinCache3b[0];
                        normals[0][1]=cosCache3a[0]*sinCache3b[0];
                        normals[0][2]=cosCache3b[0];
                        break;
                    case GLU_SMOOTH:
                        sintemp3=sinCache2b[1];
                        costemp3=cosCache2b[1];
                        normals[0][0]=sinCache2a[0]*sinCache2b[0];
                        normals[0][1]=cosCache2a[0]*sinCache2b[0];
                        normals[0][2]=cosCache2b[0];
                        break;
                    default:
                        break;
                }
                
                vertices[0][0]=0.0f;
                vertices[0][1]=0.0f;
                vertices[0][2]=radius;
                
                if (qobj->orientation==GLU_OUTSIDE)
                {
                    for (i=slices; i>=0; i--)
                    {
                        switch(qobj->normals)
                        {
                            case GLU_SMOOTH:
                                normals[slices-i+1][0]=sinCache2a[i]*sintemp3;
                                normals[slices-i+1][1]=cosCache2a[i]*sintemp3;
                                normals[slices-i+1][2]=costemp3;
                                break;
                            case GLU_FLAT:
                                if (i!=slices)
                                {
                                    normals[slices-i+1][0]=sinCache3a[i+1]*sintemp3;
                                    normals[slices-i+1][1]=cosCache3a[i+1]*sintemp3;
                                    normals[slices-i+1][2]=costemp3;
                                }
                                else
                                {
                                    /* We must add any normal here */
                                    normals[slices-i+1][0]=sinCache3a[i]*sintemp3;
                                    normals[slices-i+1][1]=cosCache3a[i]*sintemp3;
                                    normals[slices-i+1][2]=costemp3;
                                }
                                break;
                            case GLU_NONE:
                            default:
                                break;
                        }
                        vertices[slices-i+1][0]=sintemp2*sinCache1a[i];
                        vertices[slices-i+1][1]=sintemp2*cosCache1a[i];
                        vertices[slices-i+1][2]=zHigh;
                    }
                }
                else
                {
                    for (i=0; i<=slices; i++)
                    {
                        switch(qobj->normals)
                        {
                            case GLU_SMOOTH:
                                normals[i+1][0]=sinCache2a[i]*sintemp3;
                                normals[i+1][1]=cosCache2a[i]*sintemp3;
                                normals[i+1][2]=costemp3;
                                break;
                            case GLU_FLAT:
                                normals[i+1][0]=sinCache3a[i]*sintemp3;
                                normals[i+1][1]=cosCache3a[i]*sintemp3;
                                normals[i+1][2]=costemp3;
                                break;
                            case GLU_NONE:
                            default:
                                break;
                        }
                        vertices[i+1][0]=sintemp2*sinCache1a[i];
                        vertices[i+1][1]=sintemp2*cosCache1a[i];
                        vertices[i+1][2]=zHigh;
                    }
                }
                glDrawArrays(GL_TRIANGLE_FAN, 0, (slices+2));
                
                /* High end next (j==stacks-1 iteration) */
                sintemp2=sinCache1b[stacks-1];
                zHigh=cosCache1b[stacks-1];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                        sintemp3=sinCache3b[stacks];
                        costemp3=cosCache3b[stacks];
                        normals[0][0]=sinCache3a[stacks]*sinCache3b[stacks];
                        normals[0][1]=cosCache3a[stacks]*sinCache3b[stacks];
                        normals[0][2]=cosCache3b[stacks];
                        break;
                    case GLU_SMOOTH:
                        sintemp3=sinCache2b[stacks-1];
                        costemp3=cosCache2b[stacks-1];
                        normals[0][0]=sinCache2a[stacks]*sinCache2b[stacks];
                        normals[0][1]=cosCache2a[stacks]*sinCache2b[stacks];
                        normals[0][2]=cosCache2b[stacks];
                        break;
                    default:
                        break;
                }
                
                vertices[0][0]=0.0f;
                vertices[0][1]=0.0f;
                vertices[0][2]=-radius;
                
                if (qobj->orientation==GLU_OUTSIDE)
                {
                    for (i=0; i<=slices; i++)
                    {
                        switch(qobj->normals)
                        {
                            case GLU_SMOOTH:
                                normals[i+1][0]=sinCache2a[i]*sintemp3;
                                normals[i+1][1]=cosCache2a[i]*sintemp3;
                                normals[i+1][2]=costemp3;
                                break;
                            case GLU_FLAT:
                                normals[i+1][0]=sinCache3a[i]*sintemp3;
                                normals[i+1][1]=cosCache3a[i]*sintemp3;
                                normals[i+1][2]=costemp3;
                                break;
                            case GLU_NONE:
                            default:
                                break;
                        }
                        vertices[i+1][0]=sintemp2*sinCache1a[i];
                        vertices[i+1][1]=sintemp2*cosCache1a[i];
                        vertices[i+1][2]=zHigh;
                    }
                }
                else
                {
                    for (i=slices; i>=0; i--)
                    {
                        switch(qobj->normals)
                        {
                            case GLU_SMOOTH:
                                normals[slices-i+1][0]=sinCache2a[i]*sintemp3;
                                normals[slices-i+1][1]=cosCache2a[i]*sintemp3;
                                normals[slices-i+1][2]=costemp3;
                                break;
                            case GLU_FLAT:
                                if (i!=slices)
                                {
                                    normals[slices-i+1][0]=sinCache3a[i+1]*sintemp3;
                                    normals[slices-i+1][1]=cosCache3a[i+1]*sintemp3;
                                    normals[slices-i+1][2]=costemp3;
                                }
                                else
                                {
                                    normals[slices-i+1][0]=sinCache3a[i]*sintemp3;
                                    normals[slices-i+1][1]=cosCache3a[i]*sintemp3;
                                    normals[slices-i+1][2]=costemp3;
                                }
                                break;
                            case GLU_NONE:
                            default:
                                break;
                        }
                        vertices[slices-i+1][0]=sintemp2*sinCache1a[i];
                        vertices[slices-i+1][1]=sintemp2*cosCache1a[i];
                        vertices[slices-i+1][2]=zHigh;
                    }
                }
                glDrawArrays(GL_TRIANGLE_FAN, 0, (slices+2));
            }
            else
            {
                start=0;
                finish=stacks;
            }
            
            for (j=start; j<finish; j++)
            {
                zLow=cosCache1b[j];
                zHigh=cosCache1b[j+1];
                sintemp1=sinCache1b[j];
                sintemp2=sinCache1b[j+1];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                        sintemp4=sinCache3b[j+1];
                        costemp4=cosCache3b[j+1];
                        break;
                    case GLU_SMOOTH:
                        if (qobj->orientation==GLU_OUTSIDE)
                        {
                            sintemp3=sinCache2b[j+1];
                            costemp3=cosCache2b[j+1];
                            sintemp4=sinCache2b[j];
                            costemp4=cosCache2b[j];
                        }
                        else
                        {
                            sintemp3=sinCache2b[j];
                            costemp3=cosCache2b[j];
                            sintemp4=sinCache2b[j+1];
                            costemp4=cosCache2b[j+1];
                        }
                        break;
                    default:
                        break;
                }
                for (i=0; i<=slices; i++)
                {
                    switch(qobj->normals)
                    {
                        case GLU_SMOOTH:
                            normals[i*2][0]=sinCache2a[i]*sintemp3;
                            normals[i*2][1]=cosCache2a[i]*sintemp3;
                            normals[i*2][2]=costemp3;
                            break;
                        case GLU_FLAT:
                            normals[i*2][0]=sinCache3a[i]*sintemp4;
                            normals[i*2][1]=cosCache3a[i]*sintemp4;
                            normals[i*2][2]=costemp4;
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    if (qobj->orientation==GLU_OUTSIDE)
                    {
                        if (qobj->textureCoords)
                        {
                            texcoords[i*2][0]=1-(GLfloat)i/slices;
                            texcoords[i*2][1]=1-(GLfloat)(j+1)/stacks;
                        }
                        vertices[i*2][0]=sintemp2*sinCache1a[i];
                        vertices[i*2][1]=sintemp2*cosCache1a[i];
                        vertices[i*2][2]=zHigh;
                    }
                    else
                    {
                        if (qobj->textureCoords)
                        {
                            texcoords[i*2][0]=1-(GLfloat)i/slices;
                            texcoords[i*2][1]=1-(GLfloat)j/stacks;
                        }
                        vertices[i*2][0]=sintemp1*sinCache1a[i];
                        vertices[i*2][1]=sintemp1*cosCache1a[i];
                        vertices[i*2][2]=zLow;
                    }
                    switch(qobj->normals)
                    {
                        case GLU_SMOOTH:
                            normals[i*2+1][0]=sinCache2a[i]*sintemp4;
                            normals[i*2+1][1]=cosCache2a[i]*sintemp4;
                            normals[i*2+1][2]=costemp4;
                            break;
                        case GLU_FLAT:
                            normals[i*2+1][0]=sinCache3a[i]*sintemp4;
                            normals[i*2+1][1]=cosCache3a[i]*sintemp4;
                            normals[i*2+1][2]=costemp4;
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    if (qobj->orientation==GLU_OUTSIDE)
                    {
                        if (qobj->textureCoords)
                        {
                            texcoords[i*2+1][0]=1-(GLfloat)i/slices;
                            texcoords[i*2+1][1]=1-(GLfloat)j/stacks;
                        }
                        vertices[i*2+1][0]=sintemp1*sinCache1a[i];
                        vertices[i*2+1][1]=sintemp1*cosCache1a[i];
                        vertices[i*2+1][2]=zLow;
                    }
                    else
                    {
                        if (qobj->textureCoords)
                        {
                            texcoords[i*2+1][0]=1-(GLfloat)i/slices;
                            texcoords[i*2+1][1]=1-(GLfloat)(j+1)/stacks;
                        }
                        vertices[i*2+1][0]=sintemp2*sinCache1a[i];
                        vertices[i*2+1][1]=sintemp2*cosCache1a[i];
                        vertices[i*2+1][2]=zHigh;
                    }
                }
                glDrawArrays(GL_TRIANGLE_STRIP, 0, (slices+1)*2);
            }
            break;
        case GLU_POINT:
            for (j=0; j<=stacks; j++)
            {
                sintemp1=sinCache1b[j];
                costemp1=cosCache1b[j];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                    case GLU_SMOOTH:
                        sintemp2=sinCache2b[j];
                        costemp2=cosCache2b[j];
                        break;
                    default:
                        break;
                }
                
                for (i=0; i<slices; i++)
                {
                    switch(qobj->normals)
                    {
                        case GLU_FLAT:
                        case GLU_SMOOTH:
                            normals[i][0]=sinCache2a[i]*sintemp2;
                            normals[i][1]=cosCache2a[i]*sintemp2;
                            normals[i][2]=costemp2;
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    zLow=j*radius/stacks;
                    if (qobj->textureCoords)
                    {
                        texcoords[i][0]=1-(GLfloat)i/slices;
                        texcoords[i][1]=1-(GLfloat)j/stacks;
                    }
                    vertices[i][0]=sintemp1*sinCache1a[i];
                    vertices[i][1]=sintemp1*cosCache1a[i];
                    vertices[i][2]=costemp1;
                }
                glDrawArrays(GL_POINTS, 0, slices);
            }
            break;
        case GLU_LINE:
        case GLU_SILHOUETTE:
            for (j=1; j<stacks; j++)
            {
                sintemp1=sinCache1b[j];
                costemp1=cosCache1b[j];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                    case GLU_SMOOTH:
                        sintemp2=sinCache2b[j];
                        costemp2=cosCache2b[j];
                        break;
                    default:
                        break;
                }
                
                for (i=0; i<=slices; i++)
                {
                    switch(qobj->normals)
                    {
                        case GLU_FLAT:
                            normals[i][0]=sinCache3a[i]*sintemp2;
                            normals[i][1]=cosCache3a[i]*sintemp2;
                            normals[i][2]=costemp2;
                            break;
                        case GLU_SMOOTH:
                            normals[i][0]=sinCache2a[i]*sintemp2;
                            normals[i][1]=cosCache2a[i]*sintemp2;
                            normals[i][2]=costemp2;
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    if (qobj->textureCoords)
                    {
                        texcoords[i][0]=1-(GLfloat)i/slices;
                        texcoords[i][1]=1-(GLfloat)j/stacks;
                    }
                    vertices[i][0]=sintemp1*sinCache1a[i];
                    vertices[i][1]=sintemp1*cosCache1a[i];
                    vertices[i][2]=costemp1;
                }
                glDrawArrays(GL_LINE_STRIP, 0, slices+1);
            }
            
            for (i=0; i<slices; i++)
            {
                sintemp1=sinCache1a[i];
                costemp1=cosCache1a[i];
                switch(qobj->normals)
                {
                    case GLU_FLAT:
                    case GLU_SMOOTH:
                        sintemp2=sinCache2a[i];
                        costemp2=cosCache2a[i];
                        break;
                    default:
                        break;
                }
                
                for (j=0; j<=stacks; j++)
                {
                    switch(qobj->normals)
                    {
                        case GLU_FLAT:
                            normals[j][0]=sintemp2*sinCache3b[j];
                            normals[j][1]=costemp2*sinCache3b[j];
                            normals[j][2]=cosCache3b[j];
                            break;
                        case GLU_SMOOTH:
                            normals[j][0]=sintemp2*sinCache2b[j];
                            normals[j][1]=costemp2*sinCache2b[j];
                            normals[j][2]=cosCache2b[j];
                            break;
                        case GLU_NONE:
                        default:
                            break;
                    }
                    
                    if (qobj->textureCoords)
                    {
                        texcoords[j][0]=1-(GLfloat)i/slices;
                        texcoords[j][1]=1-(GLfloat)j/stacks;
                    }
                    vertices[j][0]=sintemp1*sinCache1b[j];
                    vertices[j][1]=costemp1*sinCache1b[j];
                    vertices[j][2]=cosCache1b[j];
                }
                glDrawArrays(GL_LINE_STRIP, 0, stacks+1);
            }
            break;
        default:
            break;
    }
    
    /* Disable or re-enable arrays */
    if (vertex_enabled)
    {
        /* Re-enable vertex array */
        glEnableClientState(GL_VERTEX_ARRAY);
    }
    else
    {
        glDisableClientState(GL_VERTEX_ARRAY);
    }
    
    if (texcoord_enabled)
    {
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    else
    {
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    
    if (normal_enabled)
    {
        glEnableClientState(GL_NORMAL_ARRAY);
    }
    else
    {
        glDisableClientState(GL_NORMAL_ARRAY);
    }
    
    if (color_enabled)
    {
        glEnableClientState(GL_COLOR_ARRAY);
    }
    else
    {
        glDisableClientState(GL_COLOR_ARRAY);
    }
}

