/******************************Module*Header*******************************\
* Module Name: objects.cxx
*
* Creates command lists for pipe primitive objects
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <windows.h>
#include <GL/gl.h>
#include "sscommon.h"
#include "objects.h"
#include "sspipes.h"

#define ROOT_TWO 1.414213562373f

/**************************************************************************\
* OBJECT constructor
*
\**************************************************************************/
OBJECT::OBJECT( )
{
    listNum = glGenLists(1);
}

/**************************************************************************\
* OBJECT destructor
*
\**************************************************************************/
OBJECT::~OBJECT( )
{
    glDeleteLists( listNum, 1 );
}

/**************************************************************************\
* Draw
*
* - Draw the object by calling its display list
*
\**************************************************************************/
void
OBJECT::Draw( )
{
    glCallList( listNum );
}

/**************************************************************************\
* PIPE_OBJECT constructors
*
\**************************************************************************/
PIPE_OBJECT::PIPE_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, float len )
{
    Build( pBuildInfo, len, 0.0f, 0.0f );
}

PIPE_OBJECT::PIPE_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, float len, float s_start, float s_end )
{
    Build( pBuildInfo, len, s_start, s_end );
}

/**************************************************************************\
* ELBOW_OBJECT constructors
*
\**************************************************************************/
ELBOW_OBJECT::ELBOW_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, int notch )
{
    Build( pBuildInfo, notch, 0.0f, 0.0f );
}

ELBOW_OBJECT::ELBOW_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, int notch, float s_start, float s_end )
{
    Build( pBuildInfo, notch, s_start, s_end );
}

/**************************************************************************\
* BALLJOINT_OBJECT constructor
*
\**************************************************************************/
BALLJOINT_OBJECT::BALLJOINT_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, int notch, float s_start, float s_end )
{
    Build( pBuildInfo, notch, s_start, s_end );
}

/**************************************************************************\
* SPHERE_OBJECT constructors
*
\**************************************************************************/
SPHERE_OBJECT::SPHERE_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, float radius )
{
    Build( pBuildInfo, radius, 0.0f, 0.0f );
}

SPHERE_OBJECT::SPHERE_OBJECT( OBJECT_BUILD_INFO *pBuildInfo, float radius, float s_start, float s_end )
{
    Build( pBuildInfo, radius, s_start, s_end );
}


// rotate circle around x-axis, with edge attached to anchor

static void TransformCircle( 
    float angle, 
    POINT3D *inPoint, 
    POINT3D *outPoint, 
    GLint num,
    POINT3D *anchor )
{
    MATRIX matrix1, matrix2, matrix3;
    int i;

    // translate anchor point to origin
    ss_matrixIdent( &matrix1 );
    ss_matrixTranslate( &matrix1, -anchor->x, -anchor->y, -anchor->z );

    // rotate by angle, cw around x-axis
    ss_matrixIdent( &matrix2 );
    ss_matrixRotate( &matrix2, (double) -angle, 0.0, 0.0 );

    // concat these 2
    ss_matrixMult( &matrix3, &matrix2, &matrix1 );

    // translate back
    ss_matrixIdent( &matrix2 );
    ss_matrixTranslate( &matrix2,  anchor->x,  anchor->y,  anchor->z );

    // concat these 2
    ss_matrixMult( &matrix1, &matrix2, &matrix3 );

    // transform all the points, + center
    for( i = 0; i < num; i ++, outPoint++, inPoint++ ) {
        ss_xformPoint( outPoint, inPoint, &matrix1 );
    }
}

static void CalcNormals( POINT3D *p, POINT3D *n, POINT3D *center,
                         int num )
{
    int i;

    for( i = 0; i < num; i ++, n++, p++ ) {
        n->x = p->x - center->x;
        n->y = p->y - center->y;
        n->z = p->z - center->z;
        ss_normalizeNorm( n );
    }
}

/*----------------------------------------------------------------------\
|    MakeQuadStrip()                                                    |
|       - builds quadstrip between 2 rows of points. pA points to one   |
|         row of points, and pB to the next rotated row.  Because       |
|         the rotation has previously been defined CCW around the       |
|         x-axis, using an A-B sequence will result in CCW quads        |
|                                                                       |
\----------------------------------------------------------------------*/
static void MakeQuadStrip
(
    POINT3D *pA, 
    POINT3D *pB, 
    POINT3D *nA, 
    POINT3D *nB, 
    BOOL    bTexture,
    GLfloat *tex_s,
    GLfloat *tex_t,
    GLint slices
)
{
    GLint i;

    glBegin( GL_QUAD_STRIP );

    for( i = 0; i < slices; i ++ ) {
        glNormal3fv( (GLfloat *) nA++ );
        if( bTexture )
            glTexCoord2f( tex_s[0], *tex_t );
        glVertex3fv( (GLfloat *) pA++ );
        glNormal3fv( (GLfloat *) nB++ );
        if( bTexture )
            glTexCoord2f( tex_s[1], *tex_t++ );
        glVertex3fv( (GLfloat *) pB++ );
    }

    glEnd();
}

#define CACHE_SIZE      100     


/*----------------------------------------------------------------------\
|    BuildElbow()                                                      |
|       - builds elbows, by rotating a circle in the y=r plane          |
|         centered at (0,r,-r), CW around the x-axis at anchor pt.      |
|         (r = radius of the circle)                                    |
|       - rotation is 90.0 degrees, ending at circle in z=0 plane,      |
|         centered at origin.                                           |
|       - in order to 'mate' texture coords with the cylinders          |
|         generated with glu, we generate 4 elbows, each corresponding  |
|         to the 4 possible CW 90 degree orientations of the start point|
|         for each circle.                                              |
|       - We call this start point the 'notch'.  If we characterize     |
|         each notch by the axis it points down in the starting and     |
|         ending circles of the elbow, then we get the following axis   |
|         pairs for our 4 notches:                                      |
|               - +z,+y                                                 |
|               - +x,+x                                                 |
|               - -z,-y                                                 |
|               - -x,-x                                                 |
|         Since the start of the elbow always points down +y, the 4     |
|         start notches give all possible 90.0 degree orientations      |
|         around y-axis.                                                |
|       - We can keep track of the current 'notch' vector to provide    |
|         proper mating between primitives.                             |
|       - Each circle of points is described CW from the start point,   |
|         assuming looking down the +y axis(+y direction).              |
|       - texture 's' starts at 0.0, and goes to 2.0*r/divSize at       |
|         end of the elbow.  (Then a short pipe would start with this   |
|         's', and run it to 1.0).                                      |
|                                                                       |
\----------------------------------------------------------------------*/
void
ELBOW_OBJECT::Build( OBJECT_BUILD_INFO *pBuildInfo, int notch, float s_start, float s_end )
{
    GLint   stacks, slices;
    GLfloat angle, startAng;
    GLint numPoints;
    GLfloat s_delta;
    POINT3D pi[CACHE_SIZE]; // initial row of points + center
    POINT3D p0[CACHE_SIZE]; // 2 rows of points
    POINT3D p1[CACHE_SIZE];
    POINT3D n0[CACHE_SIZE]; // 2 rows of normals
    POINT3D n1[CACHE_SIZE];
    GLfloat tex_t[CACHE_SIZE];// 't' texture coords
    GLfloat tex_s[2];  // 's' texture coords
    POINT3D center;  // center of circle
    POINT3D anchor;  // where circle is anchored
    POINT3D *pA, *pB, *nA, *nB;
    int i;
    IPOINT2D *texRep = pBuildInfo->texRep;
    GLfloat radius = pBuildInfo->radius;
    BOOL    bTexture = pBuildInfo->bTexture;

    slices = pBuildInfo->nSlices;
    stacks = slices / 2;

    if (slices >= CACHE_SIZE) slices = CACHE_SIZE-1;
    if (stacks >= CACHE_SIZE) stacks = CACHE_SIZE-1;

    s_delta = s_end - s_start;
 
    if( bTexture ) {
        // calculate 't' texture coords
        for( i = 0; i <= slices; i ++ ) {
            tex_t[i] = (GLfloat) i * texRep->y / slices;
        }
    }

    numPoints = slices + 1;

        // starting angle increment 90.0 degrees each time
        startAng = notch * PI / 2;

        // calc initial circle of points for circle centered at 0,r,-r
        // points start at (0,r,0), and rotate circle CCW

        for( i = 0; i <= slices; i ++ ) {
            angle = startAng + (2 * PI * i / slices);
            pi[i].x = radius * (float) sin(angle);
            pi[i].y = radius;
            // translate z by -r, cuz these cos calcs are for circle at origin
            pi[i].z = radius * (float) cos(angle) - radius;
        }

        // center point, tacked onto end of circle of points
        pi[i].x =  0.0f;
        pi[i].y =  radius;
        pi[i].z = -radius;
        center = pi[i];
    
        // anchor point
        anchor.x = anchor.z = 0.0f;
        anchor.y = radius;

        // calculate initial normals
        CalcNormals( pi, n0, &center, numPoints );

        // initial 's' texture coordinate
        tex_s[0] = s_start;

        // setup pointers
        pA = pi;
        pB = p0;
        nA = n0;
        nB = n1;

        // now iterate throught the stacks

        glNewList(listNum, GL_COMPILE);

        for( i = 1; i <= stacks; i ++ ) {
            // ! this angle must be negative, for correct vertex orientation !
            angle = - 0.5f * PI * i / stacks;

            // transform to get next circle of points + center
            TransformCircle( angle, pi, pB, numPoints+1, &anchor );

            // calculate normals
            center = pB[numPoints];
            CalcNormals( pB, nB, &center, numPoints );

            // calculate next 's' texture coord
            tex_s[1] = (GLfloat) s_start + s_delta * i / stacks;

            // now we've got points and normals, ready to be quadstrip'd
            MakeQuadStrip( pA, pB, nA, nB, bTexture, tex_s, tex_t, numPoints );

            // reset pointers
            pA = pB;
            nA = nB;
            pB = (pB == p0) ? p1 : p0;
            nB = (nB == n0) ? n1 : n0;
            tex_s[0] = tex_s[1];
        }

        glEndList();
}

/*----------------------------------------------------------------------\
|    BuildBallJoint()                                                  |
|       - These are very similar to the elbows, in that the starting    |
|         and ending positions are almost identical.   The difference   |
|         here is that the circles in the sweep describe a sphere as    |
|         they are rotated.                                             |
|                                                                       |
\----------------------------------------------------------------------*/
void 
BALLJOINT_OBJECT::Build( OBJECT_BUILD_INFO *pBuildInfo, int notch, 
                         float s_start, float s_end )
{
    GLfloat ballRadius;
    GLfloat angle, delta_a, startAng, theta;
    GLint numPoints;
    GLfloat s_delta;
    POINT3D pi0[CACHE_SIZE]; // 2 circles of untransformed points
    POINT3D pi1[CACHE_SIZE];
    POINT3D p0[CACHE_SIZE]; // 2 rows of transformed points
    POINT3D p1[CACHE_SIZE];
    POINT3D n0[CACHE_SIZE]; // 2 rows of normals
    POINT3D n1[CACHE_SIZE];
    float   r[CACHE_SIZE];  // radii of the circles
    GLfloat tex_t[CACHE_SIZE];// 't' texture coords
    GLfloat tex_s[2];  // 's' texture coords
    POINT3D center;  // center of circle
    POINT3D anchor;  // where circle is anchored
    POINT3D *pA, *pB, *nA, *nB;
    int i, k;
    GLint   stacks, slices;
    IPOINT2D *texRep = pBuildInfo->texRep;
    GLfloat radius = pBuildInfo->radius;
    BOOL    bTexture = pBuildInfo->bTexture;

    slices = pBuildInfo->nSlices;
    stacks = slices;

    if (slices >= CACHE_SIZE) slices = CACHE_SIZE-1;
    if (stacks >= CACHE_SIZE) stacks = CACHE_SIZE-1;

    // calculate the radii for each circle in the sweep, where
    // r[i] = y = sin(angle)/r

    angle = PI / 4;  // first radius always at 45.0 degrees
    delta_a = (PI / 2.0f) / stacks;

    ballRadius = ROOT_TWO * radius;
    for( i = 0; i <= stacks; i ++, angle += delta_a ) {
        r[i] = (float) sin(angle) * ballRadius;
    }

    if( bTexture ) {
        // calculate 't' texture coords
        for( i = 0; i <= slices; i ++ ) {
            tex_t[i] = (GLfloat) i * texRep->y / slices;
        }
    }

    s_delta = s_end - s_start;
 
    numPoints = slices + 1;

    // unlike the elbow, the center for the ball joint is constant
    center.x = center.y = 0.0f;
    center.z = -radius;

        // starting angle along circle, increment 90.0 degrees each time
        startAng = notch * PI / 2;

        // calc initial circle of points for circle centered at 0,r,-r
        // points start at (0,r,0), and rotate circle CCW

        delta_a = 2 * PI / slices;
        for( i = 0, theta = startAng; i <= slices; i ++, theta += delta_a ) {
            pi0[i].x = r[0] * (float) sin(theta);
            pi0[i].y = radius;
            // translate z by -r, cuz these cos calcs are for circle at origin
            pi0[i].z = r[0] * (float) cos(theta) - r[0];
        }

        // anchor point
        anchor.x = anchor.z = 0.0f;
        anchor.y = radius;

        // calculate initial normals
        CalcNormals( pi0, n0, &center, numPoints );

        // initial 's' texture coordinate
        tex_s[0] = s_start;

        // setup pointers
        pA = pi0; // circles of transformed points
        pB = p0;
        nA = n0; // circles of transformed normals
        nB = n1;

        // now iterate throught the stacks

        glNewList(listNum, GL_COMPILE);

        for( i = 1; i <= stacks; i ++ ) {
            // ! this angle must be negative, for correct vertex orientation !
            angle = - 0.5f * PI * i / stacks;

            // calc the next circle of untransformed points into pi1[]

            for( k = 0, theta = startAng; k <= slices; k ++, theta+=delta_a ) {
                pi1[k].x = r[i] * (float) sin(theta);
                pi1[k].y = radius;
                // translate z by -r, cuz calcs are for circle at origin
                pi1[k].z = r[i] * (float) cos(theta) - r[i];
            }

            // rotate cirle of points to next position
            TransformCircle( angle, pi1, pB, numPoints, &anchor );

            // calculate normals
            CalcNormals( pB, nB, &center, numPoints );

            // calculate next 's' texture coord
            tex_s[1] = (GLfloat) s_start + s_delta * i / stacks;

            // now we've got points and normals, ready to be quadstrip'd
            MakeQuadStrip( pA, pB, nA, nB, bTexture, tex_s, tex_t, numPoints );

            // reset pointers
            pA = pB;
            nA = nB;
            pB = (pB == p0) ? p1 : p0;
            nB = (nB == n0) ? n1 : n0;
            tex_s[0] = tex_s[1];
        }

        glEndList();
}

// 'glu' routines

#ifdef _EXTENSIONS_
#define COS cosf
#define SIN sinf
#define SQRT sqrtf
#else
#define COS cos
#define SIN sin
#define SQRT sqrt
#endif


/**************************************************************************\
* BuildCylinder
*
\**************************************************************************/
void
PIPE_OBJECT::Build( OBJECT_BUILD_INFO *pBuildInfo, float length, float s_start, 
                            float s_end )
{
    GLint   stacks, slices;
    GLint   i,j;
    GLfloat sinCache[CACHE_SIZE];
    GLfloat cosCache[CACHE_SIZE];
    GLfloat sinCache2[CACHE_SIZE];
    GLfloat cosCache2[CACHE_SIZE];
    GLfloat angle;
    GLfloat zLow, zHigh;
    GLfloat zNormal;
    GLfloat s_delta;
    IPOINT2D *texRep = pBuildInfo->texRep;
    GLfloat radius = pBuildInfo->radius;
    BOOL    bTexture = pBuildInfo->bTexture;

    slices = pBuildInfo->nSlices;
    stacks = (int) SS_ROUND_UP( (length/pBuildInfo->divSize) * (float)slices) ;

    if (slices >= CACHE_SIZE) slices = CACHE_SIZE-1;
    if (stacks >= CACHE_SIZE) stacks = CACHE_SIZE-1;

    zNormal = 0.0f;

    s_delta = s_end - s_start;

    for (i = 0; i < slices; i++) {
        angle = 2 * PI * i / slices;
        sinCache2[i] = (float) SIN(angle);
        cosCache2[i] = (float) COS(angle);
        sinCache[i] = (float) SIN(angle);
        cosCache[i] = (float) COS(angle);
    }

    sinCache[slices] = sinCache[0];
    cosCache[slices] = cosCache[0];
    sinCache2[slices] = sinCache2[0];
    cosCache2[slices] = cosCache2[0];

    glNewList(listNum, GL_COMPILE);

        for (j = 0; j < stacks; j++) {
            zLow = j * length / stacks;
            zHigh = (j + 1) * length / stacks;

            glBegin(GL_QUAD_STRIP);
            for (i = 0; i <= slices; i++) {
                    glNormal3f(sinCache2[i], cosCache2[i], zNormal);
                    if (bTexture) {
                        glTexCoord2f( (float) s_start + s_delta * j / stacks,
                                      (float) i * texRep->y / slices );
                    }
                    glVertex3f(radius * sinCache[i], 
                            radius * cosCache[i], zLow);
                    if (bTexture) {
                        glTexCoord2f( (float) s_start + s_delta*(j+1) / stacks,
                                      (float) i * texRep->y / slices );
                    }
                    glVertex3f(radius * sinCache[i], 
                            radius * cosCache[i], zHigh);
            }
            glEnd();
        }

    glEndList();
}


/*----------------------------------------------------------------------\
|    pipeSphere()                                                       |
|                                                                       |
\----------------------------------------------------------------------*/
void 
SPHERE_OBJECT::Build( OBJECT_BUILD_INFO *pBuildInfo, GLfloat radius, 
                      GLfloat s_start, GLfloat s_end)
{
    GLint i,j;
    GLfloat sinCache1a[CACHE_SIZE];
    GLfloat cosCache1a[CACHE_SIZE];
    GLfloat sinCache2a[CACHE_SIZE];
    GLfloat cosCache2a[CACHE_SIZE];
    GLfloat sinCache1b[CACHE_SIZE];
    GLfloat cosCache1b[CACHE_SIZE];
    GLfloat sinCache2b[CACHE_SIZE];
    GLfloat cosCache2b[CACHE_SIZE];
    GLfloat angle;
    GLfloat zLow, zHigh;
    GLfloat sintemp1, sintemp2, sintemp3, sintemp4;
    GLfloat costemp3, costemp4;
    GLfloat s_delta;
    GLint start, finish;
    GLint   stacks, slices;
    BOOL    bTexture = pBuildInfo->bTexture;
    IPOINT2D *texRep = pBuildInfo->texRep;

    slices = pBuildInfo->nSlices;
    stacks = slices;
    if (slices >= CACHE_SIZE) slices = CACHE_SIZE-1;
    if (stacks >= CACHE_SIZE) stacks = CACHE_SIZE-1;

    // invert sense of s - it seems the glu sphere is not built similarly
    // to the glu cylinder
    // (this probably means stacks don't grow along +z - check it out)
    s_delta = s_start;
    s_start = s_end;
    s_end = s_delta; 

    s_delta = s_end - s_start;

    /* Cache is the vertex locations cache */
    /* Cache2 is the various normals at the vertices themselves */

    for (i = 0; i < slices; i++) {
        angle = 2 * PI * i / slices;
        sinCache1a[i] = (float) SIN(angle);
        cosCache1a[i] = (float) COS(angle);
            sinCache2a[i] = sinCache1a[i];
            cosCache2a[i] = cosCache1a[i];
    }

    for (j = 0; j <= stacks; j++) {
        angle = PI * j / stacks;
                sinCache2b[j] = (float) SIN(angle);
                cosCache2b[j] = (float) COS(angle);
        sinCache1b[j] = radius * (float) SIN(angle);
        cosCache1b[j] = radius * (float) COS(angle);
    }
    /* Make sure it comes to a point */
    sinCache1b[0] = 0.0f;
    sinCache1b[stacks] = 0.0f;

    sinCache1a[slices] = sinCache1a[0];
    cosCache1a[slices] = cosCache1a[0];
        sinCache2a[slices] = sinCache2a[0];
        cosCache2a[slices] = cosCache2a[0];

    glNewList(listNum, GL_COMPILE);

        /* Do ends of sphere as TRIANGLE_FAN's (if not bTexture)
        ** We don't do it when bTexture because we need to respecify the
        ** texture coordinates of the apex for every adjacent vertex (because
        ** it isn't a constant for that point)
        */
        if (!bTexture) {
            start = 1;
            finish = stacks - 1;

            /* Low end first (j == 0 iteration) */
            sintemp2 = sinCache1b[1];
            zHigh = cosCache1b[1];
                sintemp3 = sinCache2b[1];
                costemp3 = cosCache2b[1];
                glNormal3f(sinCache2a[0] * sinCache2b[0],
                        cosCache2a[0] * sinCache2b[0],
                        cosCache2b[0]);

            glBegin(GL_TRIANGLE_FAN);
            glVertex3f(0.0f, 0.0f, radius);

                for (i = slices; i >= 0; i--) {
                        glNormal3f(sinCache2a[i] * sintemp3,
                                cosCache2a[i] * sintemp3,
                                costemp3);
                    glVertex3f(sintemp2 * sinCache1a[i],
                            sintemp2 * cosCache1a[i], zHigh);
                }
            glEnd();

            /* High end next (j == stacks-1 iteration) */
            sintemp2 = sinCache1b[stacks-1];
            zHigh = cosCache1b[stacks-1];
                sintemp3 = sinCache2b[stacks-1];
                costemp3 = cosCache2b[stacks-1];
                glNormal3f(sinCache2a[stacks] * sinCache2b[stacks],
                        cosCache2a[stacks] * sinCache2b[stacks],
                        cosCache2b[stacks]);
            glBegin(GL_TRIANGLE_FAN);
            glVertex3f(0.0f, 0.0f, -radius);
                for (i = 0; i <= slices; i++) {
                        glNormal3f(sinCache2a[i] * sintemp3,
                                cosCache2a[i] * sintemp3,
                                costemp3);
                    glVertex3f(sintemp2 * sinCache1a[i],
                            sintemp2 * cosCache1a[i], zHigh);
                }
            glEnd();
        } 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];
                    sintemp3 = sinCache2b[j+1];
                    costemp3 = cosCache2b[j+1];
                    sintemp4 = sinCache2b[j];
                    costemp4 = cosCache2b[j];

            glBegin(GL_QUAD_STRIP);
            for (i = 0; i <= slices; i++) {
                    glNormal3f(sinCache2a[i] * sintemp3,
                            cosCache2a[i] * sintemp3,
                            costemp3);
                    if (bTexture) {
                        glTexCoord2f( (float) s_start + s_delta*(j+1) / stacks,
                                      (float) i * texRep->y / slices );
                    }
                    glVertex3f(sintemp2 * sinCache1a[i],
                            sintemp2 * cosCache1a[i], zHigh);
                    glNormal3f(sinCache2a[i] * sintemp4,
                            cosCache2a[i] * sintemp4,
                            costemp4);
                    if (bTexture) {
                        glTexCoord2f( (float) s_start + s_delta * j / stacks,
                                      (float) i * texRep->y / slices );
                    }
                    glVertex3f(sintemp1 * sinCache1a[i],
                            sintemp1 * cosCache1a[i], zLow);
            }
            glEnd();
        }

    glEndList();
}
