/******************************Module*Header*******************************\
* Module Name: npipe.cxx
*
* Normal pipes code
*
* Copyright (c) 1995 Microsoft Corporation
*
\**************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include <windows.h>

#include "sspipes.h"
#include "npipe.h"
#include "state.h"


static void align_notch( int newDir, int notch );
static void align_plusy( int oldDir, int newDir );

// defCylNotch shows where the notch for the default cylinder will be,
//  in absolute coords, once we do an align_plusz

static GLint defCylNotch[NUM_DIRS] = 
        { PLUS_Y, PLUS_Y, MINUS_Z, PLUS_Z, PLUS_Y, PLUS_Y };


/**************************************************************************\
* NORMAL_PIPE constructor
*
*
\**************************************************************************/

NORMAL_PIPE::NORMAL_PIPE( STATE *pState )
: PIPE( pState )
{

    type = TYPE_NORMAL;
    pNState = pState->pNState;

    // choose weighting of going straight
    if( ! ss_iRand( 20 ) )
        weightStraight = ss_iRand2( MAX_WEIGHT_STRAIGHT/4, MAX_WEIGHT_STRAIGHT );
    else
        weightStraight = 1 + ss_iRand( 4 );
}

/**************************************************************************\
* Start
*
* Start drawing a new normal pipe
*
* - Draw a start cap and short pipe in new direction
*
\**************************************************************************/

void
NORMAL_PIPE::Start( )
{
    int newDir;

    // Set start position

    if( !SetStartPos() ) {
        status = PIPE_OUT_OF_NODES;
        return;
    }

    // set a material

    ChooseMaterial();

    // push matrix that has initial zTrans and rotation
    glPushMatrix();

    // Translate to current position
    TranslateToCurrentPosition();

    // Pick a random lastDir
    lastDir = ss_iRand( NUM_DIRS );

    newDir = ChooseNewDirection();

    if( newDir == DIR_NONE ) {
        // pipe is stuck at the start node, draw something
        status = PIPE_STUCK;
        DrawTeapot();
        glPopMatrix();
        return;
    } else
        status = PIPE_ACTIVE;

    // set initial notch vector
    notchVec = defCylNotch[newDir];

    DrawStartCap( newDir );

    // move ahead 1.0*r to draw pipe
    glTranslatef( 0.0f, 0.0f, radius );
            
    // draw short pipe
    align_notch( newDir, notchVec );
    pNState->shortPipe->Draw();

    glPopMatrix();

    UpdateCurrentPosition( newDir );

    lastDir = newDir;
}

/**************************************************************************\
* Draw
*
* - if turning, draws a joint and a short cylinder, otherwise
*   draws a long cylinder.
* - the 'current node' is set as the one we draw thru the NEXT
*   time around.
*
\**************************************************************************/

void
NORMAL_PIPE::Draw()
{
    int newDir;

    newDir = ChooseNewDirection();

    if( newDir == DIR_NONE ) {  // no empty nodes - nowhere to go
        DrawEndCap();
        status = PIPE_STUCK;
        return;
    }

    // push matrix that has initial zTrans and rotation
    glPushMatrix();

    // Translate to current position
    TranslateToCurrentPosition();

    // draw joint if necessary, and pipe

    if( newDir != lastDir ) { // turning! - we have to draw joint
        DrawJoint( newDir );

        // draw short pipe
        align_notch( newDir, notchVec );
        pNState->shortPipe->Draw();
    }
    else {  // no turn
        // draw long pipe, from point 1.0*r back
        align_plusz( newDir );
        align_notch( newDir, notchVec );
        glTranslatef( 0.0f, 0.0f, -radius );
        pNState->longPipe->Draw();
    }

    glPopMatrix();

    UpdateCurrentPosition( newDir );

    lastDir = newDir;
}

/**************************************************************************\
* DrawStartCap
*
* Cap the start of the pipe with a ball
*
\**************************************************************************/

void 
NORMAL_PIPE::DrawStartCap( int newDir )
{
    if( bTexture ) {
        align_plusz( newDir );
        pNState->ballCap->Draw();
    }
    else {
        // draw big ball in default orientation
        pNState->bigBall->Draw();
        align_plusz( newDir );
    }
}

/**************************************************************************\
* DrawEndCap():
*
* - Draws a ball, used to cap end of a pipe
*
\**************************************************************************/

void 
NORMAL_PIPE::DrawEndCap( )
{
    glPushMatrix();

    // Translate to current position
    TranslateToCurrentPosition();

    if( bTexture ) {
        glPushMatrix();
        align_plusz( lastDir );
        align_notch( lastDir, notchVec );
        pNState->ballCap->Draw();
        glPopMatrix();
    }
    else
        pNState->bigBall->Draw();

    glPopMatrix();
}

/**************************************************************************\
* ChooseElbow
*
* - Decides which elbow to draw
* - The beginning of each elbow is aligned along +y, and we have
*   to choose the one with the notch in correct position
* - The 'primary' start notch (elbow[0]) is in same direction as
*   newDir, and successive elbows rotate this notch CCW around +y
*
\**************************************************************************/


// this array supplies the sequence of elbow notch vectors, given
//  oldDir and newDir  (0's are don't cares)
// it is also used to determine the ending notch of an elbow
static GLint notchElbDir[NUM_DIRS][NUM_DIRS][4] = {
// oldDir = +x
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
        PLUS_Y,         MINUS_Z,        MINUS_Y,        PLUS_Z,
        MINUS_Y,        PLUS_Z,         PLUS_Y,         MINUS_Z,
        PLUS_Z,         PLUS_Y,         MINUS_Z,        MINUS_Y,
        MINUS_Z,        MINUS_Y,        PLUS_Z,         PLUS_Y,
// oldDir = -x
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
        PLUS_Y,         PLUS_Z,         MINUS_Y,        MINUS_Z,
        MINUS_Y,        MINUS_Z,        PLUS_Y,         PLUS_Z,
        PLUS_Z,         MINUS_Y,        MINUS_Z,        PLUS_Y,
        MINUS_Z,        PLUS_Y,         PLUS_Z,         MINUS_Y,
// oldDir = +y
        PLUS_X,         PLUS_Z,         MINUS_X,        MINUS_Z,
        MINUS_X,        MINUS_Z,        PLUS_X,         PLUS_Z,
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
        PLUS_Z,         MINUS_X,        MINUS_Z,        PLUS_X,
        MINUS_Z,        PLUS_X,         PLUS_Z,         MINUS_X,
// oldDir = -y
        PLUS_X,         MINUS_Z,        MINUS_X,        PLUS_Z,
        MINUS_X,        PLUS_Z,         PLUS_X,         MINUS_Z,
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
        PLUS_Z,         PLUS_X,         MINUS_Z,        MINUS_X,
        MINUS_Z,        MINUS_X,        PLUS_Z,         PLUS_X,
// oldDir = +z
        PLUS_X,         MINUS_Y,        MINUS_X,        PLUS_Y,
        MINUS_X,        PLUS_Y,         PLUS_X,         MINUS_Y,
        PLUS_Y,         PLUS_X,         MINUS_Y,        MINUS_X,
        MINUS_Y,        MINUS_X,        PLUS_Y,         PLUS_X,
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX,
// oldDir = -z
        PLUS_X,         PLUS_Y,         MINUS_X,        MINUS_Y,
        MINUS_X,        MINUS_Y,        PLUS_X,         PLUS_Y,
        PLUS_Y,         MINUS_X,        MINUS_Y,        PLUS_X,
        MINUS_Y,        PLUS_X,         PLUS_Y,         MINUS_X,
        iXX,            iXX,            iXX,            iXX,
        iXX,            iXX,            iXX,            iXX
};

GLint 
NORMAL_PIPE::ChooseElbow( int oldDir, int newDir )
{
    int i;

    // precomputed table supplies correct elbow orientation
    for( i = 0; i < 4; i ++ ) {
        if( notchElbDir[oldDir][newDir][i] == notchVec )
            return i;
    }
    // we shouldn't arrive here
    return -1;
}

/**************************************************************************\
* DrawJoint
*
* Draw a joint between 2 pipes
*
\**************************************************************************/

void
NORMAL_PIPE::DrawJoint( int newDir )
{
    int jointType;
    int iBend;

    jointType = pNState->ChooseJointType();
#if PIPES_DEBUG
    if( newDir == oppositeDir[lastDir] )
        printf( "Warning: opposite dir chosen!\n" );
#endif
        
    switch( jointType ) {
      case BALL_JOINT:
            if( bTexture ) {
                // use special texture-friendly ballJoints

                align_plusz( newDir );
                glPushMatrix();

                align_plusy( lastDir, newDir );

                // translate forward 1.0*r along +z to get set for drawing elbow
                glTranslatef( 0.0f, 0.0f, radius );
                // decide which elbow orientation to use
                iBend = ChooseElbow( lastDir, newDir );
                pNState->ballJoints[iBend]->Draw();

                glPopMatrix();
            }
            else {
                // draw big ball in default orientation
                pNState->bigBall->Draw();
                align_plusz( newDir );
            }
            // move ahead 1.0*r to draw pipe
            glTranslatef( 0.0f, 0.0f, radius );
        break;

      case ELBOW_JOINT:
            align_plusz( newDir );

            // the align_plusy() here will screw up our notch calcs, so
            //  we push-pop

            glPushMatrix();

            align_plusy( lastDir, newDir );

            // translate forward 1.0*r along +z to get set for drawing elbow
            glTranslatef( 0.0f, 0.0f, radius );
            // decide which elbow orientation to use
            iBend = ChooseElbow( lastDir, newDir );
            if( iBend == -1 ) {
#if PIPES_DEBUG
                printf( "ChooseElbow() screwed up\n" );
#endif
                iBend = 0; // recover
            }
            pNState->elbows[iBend]->Draw();

            glPopMatrix();

            glTranslatef( 0.0f, 0.0f, radius );
        break;

      default:
            // Horrors! It's the teapot!
            DrawTeapot();
            align_plusz( newDir );
            // move ahead 1.0*r to draw pipe
            glTranslatef( 0.0f, 0.0f, radius );
        }
            
        // update the current notch vector
        notchVec = notchTurn[lastDir][newDir][notchVec];
#if PIPES_DEBUG
        if( notchVec == iXX )
            printf( "notchTurn gave bad value\n" );
#endif
}


/**************************************************************************\
* Geometry functions
\**************************************************************************/


static float RotZ[NUM_DIRS][NUM_DIRS] = {
          0.0f,   0.0f,  90.0f,  90.0f,  90.0f, -90.0f,
          0.0f,   0.0f, -90.0f, -90.0f, -90.0f,  90.0f,
        180.0f, 180.0f,   0.0f,   0.0f, 180.0f, 180.0f,
          0.0f,   0.0f,   0.0f,   0.0f,   0.0f,   0.0f,
        -90.0f,  90.0f,   0.0f, 180.0f,   0.0f,   0.0f,
         90.0f, -90.0f, 180.0f,   0.0f,   0.0f,   0.0f };

        
            
/*-----------------------------------------------------------------------
|                                                                       |
|    align_plusy( int lastDir, int newDir )                             |
|       - Assuming +z axis is already aligned with newDir, align        |
|         +y axis BACK along lastDir                                    |
|                                                                       |
-----------------------------------------------------------------------*/

static void 
align_plusy( int oldDir, int newDir )
{
    GLfloat rotz;

    rotz = RotZ[oldDir][newDir];
    glRotatef( rotz, 0.0f, 0.0f, 1.0f );
}

// given a dir, determine how much to rotate cylinder around z to match notches
// format is [newDir][notchVec]

static GLfloat alignNotchRot[NUM_DIRS][NUM_DIRS] = {
        fXX,    fXX,    0.0f,   180.0f,  90.0f, -90.0f,
        fXX,    fXX,    0.0f,   180.0f,  -90.0f, 90.0f,
        -90.0f, 90.0f,  fXX,    fXX,    180.0f, 0.0f,
        -90.0f, 90.0f,  fXX,    fXX,    0.0f,   180.0f,
        -90.0f, 90.0f,  0.0f,   180.0f, fXX,    fXX,
        90.0f,  -90.0f, 0.0f,   180.0f, fXX,    fXX
};
                
                
/*-----------------------------------------------------------------------
|                                                                       |
|    align_notch( int newDir )                                          |
|       - a cylinder is notched, and we have to line this up            |
|         with the previous primitive's notch which is maintained as    |
|         notchVec.                                                     |
|       - this adds a rotation around z to achieve this                 |
|                                                                       |
-----------------------------------------------------------------------*/

static void 
align_notch( int newDir, int notch )
{
    GLfloat rotz;
    GLint curNotch;

    // figure out where notch is presently after +z alignment
    curNotch = defCylNotch[newDir];
    // (don't need this now we have lut)

    // look up rotation value in table
    rotz = alignNotchRot[newDir][notch];
#if PIPES_DEBUG
    if( rotz == fXX ) {
        printf( "align_notch(): unexpected value\n" );
        return;
    }
#endif

    if( rotz != 0.0f )
        glRotatef( rotz, 0.0f, 0.0f, 1.0f );
}
