//-----------------------------------------------------------------------------
// File: eval.cpp
//
// Desc: EVAL class
//       Evaluator composed of one or more sections that are evaluated
//       separately with OpenGL evaluators
//
// Copyright (c) 1994-2000 Microsoft Corporation
//-----------------------------------------------------------------------------
#include "stdafx.h"

typedef enum 
{
    X_PLANE = 0,
    Y_PLANE,
    Z_PLANE
};

#define EVAL_VSIZE 3  // vertex size in floats

#define TMAJOR_ORDER 2
#define TMINOR_ORDER 2

#define VDIM 3
#define TDIM 2

static void RotatePointSet( D3DXVECTOR3 *inPts, int numPts, float angle, int dir, 
                      float radius, D3DXVECTOR3 *outPts );
static void ExtrudePointSetDir( D3DXVECTOR3 *inPts, int numPts, float *acPts, 
                      int dir, D3DXVECTOR3 *outPts );




//-----------------------------------------------------------------------------
// Name: EVAL
// Desc: Evaluator constructor
//-----------------------------------------------------------------------------
EVAL::EVAL( BOOL bTex )
{
    m_bTexture = bTex; 

    // Allocate points buffer

    //mf: might want to use less than max in some cases
    int size = MAX_USECTIONS * MAX_UORDER * MAX_VORDER * sizeof(D3DXVECTOR3);
    m_pts = (D3DXVECTOR3 *) LocalAlloc( LMEM_FIXED, size );
    assert( m_pts != NULL && "EVAL constructor\n" );
    
    // Alloc texture points buffer
    if( m_bTexture ) 
    {
        size = MAX_USECTIONS * TEX_ORDER * TEX_ORDER * sizeof(TEX_POINT2D);
        m_texPts = (TEX_POINT2D *) LocalAlloc( LMEM_FIXED, size );
        assert( m_texPts != NULL && "EVAL constructor\n" );
    }
    
    ResetEvaluator( m_bTexture );
}




//-----------------------------------------------------------------------------
// Name: ~EVAL
// Desc: Evaluator destructor
//-----------------------------------------------------------------------------
EVAL::~EVAL( )
{
    LocalFree( m_pts );
    if( m_bTexture )
        LocalFree( m_texPts );
}




//-----------------------------------------------------------------------------
// Name: Reset
// Desc: Reset evaluator to generate 3d vertices and vertex normals
//-----------------------------------------------------------------------------
void ResetEvaluator( BOOL bTexture )
{
/*
    if( bTexture ) 
    {
        glEnable( GL_MAP2_TEXTURE_COORD_2 );
    }

    glEnable( GL_MAP2_VERTEX_3 );
    glEnable( GL_AUTO_NORMAL );
    glFrontFace( GL_CW ); // cuz
*/

    // mf: !!! if mixing Normal and Flex, have to watch out for this, cuz normal
    // needs CCW
}




//-----------------------------------------------------------------------------
// Name: SetTextureControlPoints
// Desc: Set texture control point net
//       
//       This sets up 'numSections' sets of texture coordinate control points, based
//       on starting and ending s and t values.
//       
//       s coords run along pipe direction, t coords run around circumference
//-----------------------------------------------------------------------------
void EVAL::SetTextureControlPoints( float s_start, float s_end, 
                                    float t_start, float t_end )
{
    int i;
    TEX_POINT2D *ptexPts = m_texPts;
    float t_delta = (t_end - t_start) / m_numSections;
    float t = t_start;

    // calc ctrl pts for each quadrant
    for( i = 0; i < m_numSections; i++, ptexPts += (TDIM*TDIM) ) 
    {
        // s, t coords
        ptexPts[0].t = ptexPts[2].t = t;
        t += t_delta;
        ptexPts[1].t = ptexPts[3].t = t;
        ptexPts[0].s = ptexPts[1].s = s_start;
        ptexPts[2].s = ptexPts[3].s = s_end;
    } 
}




//-----------------------------------------------------------------------------
// Name: SetVertexCtrlPtsXCTranslate
// Desc: Builds 3D control eval control net from 2 xcObjs displaced along the
//       z-axis by 'length'.
//       
//       First xc used to generate points in z=0 plane.
//       Second xc generates points in z=length plane.
//       ! Replicates the last point around each u.
//-----------------------------------------------------------------------------
void EVAL::SetVertexCtrlPtsXCTranslate( D3DXVECTOR3 *pts, float length, 
                                        XC *xcStart, XC *xcEnd )
{
    int i;
    D3DXVECTOR2 *ptsStart, *ptsEnd;
    D3DXVECTOR3 *pts1, *pts2;
    int     numPts = xcStart->m_numPts;

    numPts++;  // due to last point replication

    ptsStart = xcStart->m_pts;
    ptsEnd   = xcEnd->m_pts;
    pts1     = pts;
    pts2     = pts + numPts;

    for( i = 0; i < (numPts-1); i++, pts1++, pts2++ ) 
    {
        // copy over x,y from each xc
        *( (D3DXVECTOR2 *) pts1) = *ptsStart++;
        *( (D3DXVECTOR2 *) pts2) = *ptsEnd++;
        // set z for each
        pts1->z = 0.0f;
        pts2->z = length;
    }

    // Replicate last point in each u-band
    *pts1 = *pts;
    *pts2 = *(pts + numPts);
}




//-----------------------------------------------------------------------------
// Name: ProcessXCPrimLinear
// Desc: Processes a prim according to evaluator data
//       - Only valid for colinear xc's (along z)
//       - XC's may be identical (extrusion).  If not identical, may have
//         discontinuities at each end.
//       - Converts 2D XC pts to 3D pts
//-----------------------------------------------------------------------------
void EVAL::ProcessXCPrimLinear( XC *xcStart, XC *xcEnd, float length )
{
    if( length <= 0.0f )
        // nuttin' to do
        return;

    // Build a vertex control net from 2 xcObj's a distance 'length' apart
    // this will displace the end xcObj a distance 'length' down the z-axis
    SetVertexCtrlPtsXCTranslate( m_pts, length, xcStart, xcEnd );

    Evaluate( );
}




//-----------------------------------------------------------------------------
// Name: ProcessXCPrimBendSimple
// Desc: Processes a prim by bending along dir from xcCur
//       - dir is relative from xc in x-y plane
//       - adds C2 continuity at ends
//-----------------------------------------------------------------------------
void EVAL::ProcessXCPrimBendSimple( XC *xcCur, int dir, float radius )
{
    D3DXVECTOR3 *ptsSrc, *ptsDst;
    static float acPts[MAX_XC_PTS+1];
    int ptSetStride = xcCur->m_numPts + 1; // pt stride for output pts buffer

    // We will be creating 4 cross-sectional control point sets here.

    // Convert 2D pts in xcCur to 3D pts at z=0 for 1st point set
    xcCur->ConvertPtsZ( m_pts, 0.0f );

    // Calc 4th point set by rotating 1st set as per dir
    ptsDst = m_pts + 3*ptSetStride;
    RotatePointSet( m_pts, ptSetStride, 90.0f, dir, radius, ptsDst );

    // angles != 90, hard, cuz not easy to extrude 3rd set from 4th

    // Next, have to figure out ac values.  Need to extend each xc's points
    // into bend to generate ac net.  For circular bend (and later for general
    // case elliptical bend), need to know ac distance from xc for each point.
    // This is based on the point's turn radius - a function of its distance
    // from the 'hinge' of the turn.

    // Can take advantage of symmetry here.  Figure for one xc, good for 2nd.
    // This assumes 90 deg turn.  (also,last point replicated)
    xcCur->CalcArcACValues90( dir, radius, acPts );
    
    // 2) extrude each point's ac from xcCur (extrusion in +z)
    // apply values to 1st to get 2nd
    // MINUS_Z, cuz subtracts *back* from dir
    ExtrudePointSetDir( m_pts, ptSetStride, acPts, MINUS_Z, 
                                                    m_pts + ptSetStride );

    // 3) extrude each point's ac from xcEnd (extrusion in -dir)
    ptsSrc = m_pts + 3*ptSetStride;
    ptsDst = m_pts + 2*ptSetStride;
    ExtrudePointSetDir( ptsSrc, ptSetStride, acPts, dir, ptsDst );

    Evaluate();
}




//-----------------------------------------------------------------------------
// Name: EVAL::ProcessXCPrimSingularity
// Desc: Processes a prim by joining singularity to an xc
//       - Used for closing or opening the pipe
//       - If bOpening is true, starts with singularity, otherwise ends with one
//       - the xc side is always in z=0 plane
//       - singularity side is radius on either side of xc
//       - adds C2 continuity at ends (perpendicular to +z at singularity end)
//-----------------------------------------------------------------------------
void EVAL::ProcessXCPrimSingularity( XC *xcCur, float length, BOOL bOpening )
{
    D3DXVECTOR3 *ptsSing, *ptsXC;
    static float acPts[MAX_XC_PTS+1];
    float zSing; // z-value at singularity
    int ptSetStride = xcCur->m_numPts + 1; // pt stride for output pts buffer
    int i;
    XC xcSing(xcCur);

    // create singularity xc - which is an extremely scaled-down version
    //  of xcCur (this prevents any end-artifacts, unless of course we were
    //  to zoom it ultra-large).

    xcSing.Scale( .0005f );

    // We will be creating 4 cross-sectional control point sets here.
    // mf: 4 is like hard coded; what about for different xc component levels ?

    if( bOpening ) 
    {
        ptsSing = m_pts;
        ptsXC = m_pts + 3*ptSetStride;
    } 
    else 
    {
        ptsSing = m_pts + 3*ptSetStride;
        ptsXC = m_pts;
    }

    // Convert 2D pts in xcCur to 3D pts at 'xc' point set
    xcCur->ConvertPtsZ( ptsXC, 0.0f );

    // Set z-value for singularity point set
    zSing = bOpening ? -length : length;
    xcSing.ConvertPtsZ( ptsSing, zSing );

    // The arc control for each point is based on a radius value that is
    //  each xc point's distance from the xc center
    xcCur->CalcArcACValuesByDistance( acPts );

    // Calculate point set near xc
    if( bOpening )
        ExtrudePointSetDir( ptsXC, ptSetStride, acPts, PLUS_Z, 
                                                    ptsXC - ptSetStride );
    else
        ExtrudePointSetDir( ptsXC, ptSetStride, acPts, MINUS_Z, 
                                                    ptsXC + ptSetStride );

    // Point set near singularity is harder, as the points must generate
    // a curve between the singularity and each xc point
    // No, easier, just scale each point by universal arc controller !
    D3DXVECTOR3* ptsDst = m_pts;
    ptsDst = bOpening ? ptsSing + ptSetStride : ptsSing - ptSetStride;
    for( i = 0; i < ptSetStride; i ++, ptsDst++ ) 
    {
        ptsDst->x = EVAL_CIRC_ARC_CONTROL * ptsXC[i].x;
        ptsDst->y = EVAL_CIRC_ARC_CONTROL * ptsXC[i].y;
        ptsDst->z = zSing;
    }

    Evaluate();
}




//-----------------------------------------------------------------------------
// Name: Evaluate
// Desc: Evaluates the EVAL object
//       - There may be 1 or more lengthwise sections around an xc
//       - u is minor, v major
//       - u,t run around circumference, v,s lengthwise
//       - Texture maps are 2x2 for each section
//       - ! uDiv is per section !
//-----------------------------------------------------------------------------
void EVAL::Evaluate()
{
    int i;
    D3DXVECTOR3 *ppts = m_pts; 
    TEX_POINT2D *ptexPts = m_texPts;
    // total # pts in cross-section:
    int xcPointCount = (m_uOrder-1)*m_numSections + 1;

    for( i = 0; i < m_numSections; i ++, 
                                 ppts += (m_uOrder-1),
                                 ptexPts += (TEX_ORDER*TEX_ORDER) ) 
    {
/*
        // map texture coords
        if( bTexture ) 
        {
            glMap2f(GL_MAP2_TEXTURE_COORD_2, 
                    0.0f, 1.0f, TDIM, TEX_ORDER, 
                    0.0f, 1.0f, TEX_ORDER*TDIM, TEX_ORDER, 
                    (float *) ptexPts );
        }

        // map vertices
        glMa
        
          p2f(GL_MAP2_VERTEX_3, 
               0.0f, 1.0f, VDIM, uOrder, 
               0.0f, 1.0f, xcPointCount*VDIM, vOrder,
               (float *) ppts );

        // evaluate
        glMapGrid2f(uDiv, 0.0f, 1.0f, ``vDiv, 0.0f, 1.0f);
        glEvalMesh2( GL_FILL, 0, uDiv, 0, vDiv);
*/
    }
}




//-----------------------------------------------------------------------------
// Name: ExtrudePointSetDir
// Desc: Extrude a point set back from the current direction
//       Generates C2 continuity at the supplied point set xc, by generating another
//       point set back of the first, using supplied subtraction values.
//-----------------------------------------------------------------------------
static void ExtrudePointSetDir( D3DXVECTOR3 *inPts, int numPts, float *acPts, int dir, 
                                D3DXVECTOR3 *outPts )
{
    int i;
    float sign;
    int offset;

    switch( dir ) 
    {
        case PLUS_X:
            offset = 0;
            sign = -1.0f;
            break;
        case MINUS_X:
            offset = 0;
            sign =  1.0f;
            break;
        case PLUS_Y:
            offset = 1;
            sign = -1.0f;
            break;
        case MINUS_Y:
            offset = 1;
            sign =  1.0f;
            break;
        case PLUS_Z:
            offset = 2;
            sign = -1.0f;
            break;
        case MINUS_Z:
            offset = 2;
            sign =  1.0f;
            break;
    }

    for( i = 0; i < numPts; i++, inPts++, outPts++, acPts++ ) 
    {
        *outPts = *inPts;
        ((float *)outPts)[offset] = ((float *)inPts)[offset] + (sign * (*acPts));
    }
}




//-----------------------------------------------------------------------------
// Name: RotatePointSet
// Desc: Rotate point set by angle, according to dir and radius
//       - Put points in supplied outPts buffer
//-----------------------------------------------------------------------------
static void RotatePointSet( D3DXVECTOR3 *inPts, int numPts, float angle, int dir, 
                            float radius, D3DXVECTOR3 *outPts )
{
    D3DXMATRIX matrix1, matrix2, matrix3;
    int i;
    D3DXVECTOR3 rot = D3DXVECTOR3(0, 0, 0);
    D3DXVECTOR3 anchor = D3DXVECTOR3(0, 0, 0);

    //  dir      rot
    //  +x       90 y
    //  -x       -90 y
    //  +y       -90 x
    //  -y       90 x

    // convert angle to radians
    //mf: as noted in objects.c, we have to take negative angle to make
    // it work in familiar 'CCW rotation is positive' mode.  The ss_* rotate
    // routines must work in the 'CW is +'ve' mode, as axis pointing at you.
    angle = SS_DEG_TO_RAD(-angle);

    // set axis rotation and anchor point
    switch( dir ) 
    {
        case PLUS_X:
            rot.y = angle;
            anchor.x = radius;
            break;
        case MINUS_X:
            rot.y = -angle;
            anchor.x = -radius;
            break;
        case PLUS_Y:
            rot.x = -angle;
            anchor.y = radius;
            break;
        case MINUS_Y:
            rot.x = angle;
            anchor.y = -radius;
            break;
    }

    // translate anchor point to origin
    D3DXMatrixIdentity( &matrix1 );
    D3DXMatrixTranslation( &matrix1, -anchor.x, -anchor.y, -anchor.z );

    // rotate 
    D3DXMatrixIdentity( &matrix2 );
    D3DXMatrixRotationYawPitchRoll( &matrix2, rot.y, rot.x, rot.z ); // TODO: right?

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

    // translate back
    D3DXMatrixIdentity( &matrix2 );
    D3DXMatrixTranslation( &matrix2,  anchor.x,  anchor.y,  anchor.z );

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

    for( i = 0; i < numPts; i ++, outPts++, inPts++ ) 
    {
        // D3DXVec3TransformCoord( &tmp, inPts, &matrix1 ); // TODO: which?

        D3DXVECTOR4 tmp;
        D3DXVec3Transform( &tmp, inPts, &matrix1 );
        outPts->x = tmp.x;
        outPts->y = tmp.y;
        outPts->z = tmp.z;
    }
}