/******************************Module*Header*******************************\
* Module Name: genlem.c
*
* The Twist style of the 3D Flying Objects screen saver.
*
* Solid model of a 3D lemniscate.
*
* Copyright (c) 1994 Microsoft Corporation
*
\**************************************************************************/

#include <stdlib.h>
#include <windows.h>
//#include <GL\gl.h>
#include <string.h>
#include <math.h>
//#include "ss3dfo.h"
#include <d3dx8.h>
#include "D3DSaver.h"
#include "FlyingObjects.h"
#include "mesh.h"

#define ROT_PREC    10
#define NORMS(x, y) lemMesh.norms[((x) * iPrec) + y]
#define GRID(x, y)  lemMesh.pts[((x) * iPrec) + y]

static MESH lemMesh;
static POINT3D basis[ROT_PREC];
static double zrot = 0.2;
static int iPrec = 32;
static double *lemX;
static double *lemY;
static double *lemXT;
static double *lemYT;


static void getLem(double index, double max, double *angle, double *r)
{    
    double a, sina;

    a = (index * PI) / (max - 1.0);
    if (a >= PI)
        a -= PI;
    if (a > PI / 2.0) {
        *angle = (2.0 * PI) - a;
        sina = sin( 2.0 * *angle );
        if( sina < 0.0 )
            sina = 0.0; // protect against sqrt fpe
        *r = 0.5 * sqrt(sina);
    } else {
        *angle = a;
        sina = sin( 2.0 * *angle );
        if( sina < 0.0 )
            sina = 0.0;
        *r = 0.5 * sqrt(sina);
    }
}            


static void initLemCoords(int iMax)
{
    int i;
    double max = (double)iMax;
    double angle;
    double r;

    for (i = 0; i < iMax; i++) {
        getLem((double)i, (double)iPrec, &angle, &r);        
        lemX[i] = r * cos(angle);
        lemY[i] = r * sin(angle);
        
        getLem((double)i + 0.00001, (double)iPrec, &angle, &r);        
        lemXT[i] = r * cos(angle);
        lemYT[i] = r * sin(angle);

    }
}


void genLemniscate(void)
{
    int i;
    int j;
    double posInc = 2.0 / (float)iPrec;
    int facecount = 0;
    int ptcount = 0;
    POINT3D norm;
    static float twistFact = 0.0f;
    static float twistFactAdd = 0.05f;
    POINT3D a[ROT_PREC];
    POINT3D b[ROT_PREC];
    MATRIX matrix;
    MESH *mesh = &lemMesh;

    mesh->numPoints = 0;   
    mesh->numFaces = 0;   
    for (i = 0; i < (iPrec - 1) * (ROT_PREC - 1); i++)
        mesh->norms[i] = ss_ptZero;

    for (i = 0; i < (iPrec - 1); i++) {
        double x1, y1, x2, y2;
        double len;
        double sinAngle;
        double rotZ;
        int id[4];

        x1 = lemX[i];
        y1 = lemY[i];
        x2 = lemXT[i];
        y2 = lemYT[i];

        x2 -= x1;
        y2 -= y1;

        len = sqrt(x2 * x2 + y2 * y2);
        if (len > 0.0)
            sinAngle = y2 / len;
        else
            sinAngle = 0.0;
        if (y2 < 0.0)
            sinAngle = -sinAngle;
        rotZ = asin(sinAngle);
        if (x2 < 0.0)
            rotZ = PI - rotZ;
        if (y2 < 0.0)
            rotZ = -rotZ;
        if (rotZ < 0.0)
            rotZ = 2.0 * PI + rotZ;

        ss_matrixIdent(&matrix);
        ss_matrixRotate(&matrix, 0.0, 0.0, -rotZ);
        ss_matrixTranslate(&matrix, x1, y1, 
                         twistFact * cos((2.0 * PI * (float)i) / ((float)iPrec - 1)));
        
        for (j = 0; j < ROT_PREC; j++)
            ss_xformPoint(&a[j], &basis[j], &matrix);

        x1 = lemX[i+1];
        y1 = lemY[i+1];

        x2 = lemXT[i+1];
        y2 = lemYT[i+1];

        x2 -= x1;
        y2 -= y1;

        len = sqrt(x2 * x2 + y2 * y2);

        if (len > 0.0)
            sinAngle = y2 / len;
        else
            sinAngle = 0.0;
        if (y2 < 0.0)
            sinAngle = -sinAngle;
        rotZ = asin(sinAngle);
        if (x2 < 0.0)
            rotZ = PI - rotZ;
        if (y2 < 0.0)
            rotZ = -rotZ;
        if (rotZ < 0.0)
            rotZ = 2.0 * PI + rotZ;

        ss_matrixIdent(&matrix);
        ss_matrixRotate(&matrix, 0.0, 0.0, -rotZ);        
        ss_matrixTranslate(&matrix, x1, y1, 
                         twistFact * cos((2.0 * PI * ((float)i + 1.0)) / ((float)iPrec - 1)));

        for (j = 0; j < ROT_PREC; j++)
            ss_xformPoint(&b[j], &basis[j], &matrix);
            
        memcpy(&mesh->pts[ptcount], &a, sizeof(POINT3D) * (ROT_PREC - 1));
        ptcount += (ROT_PREC - 1);
        mesh->numPoints += (ROT_PREC - 1);
        
            
        for (j = 0; j < (ROT_PREC - 1); j++) {
            int k;
            int jj;
            
            if (j == (ROT_PREC - 2))
                jj = 0;
            else
                jj = j + 1;

            ss_calcNorm(&norm, &b[j + 1], &b[j], &a[j]);
            
            mesh->faces[facecount].material = 3;
            mesh->faces[facecount].norm = norm;
            if (i == iPrec - 2) {
                id[0] = mesh->faces[facecount].p[0] = j;
                id[1] = mesh->faces[facecount].p[1] = jj;
            } else {
                id[0] = mesh->faces[facecount].p[0] = ptcount + j;
                id[1] = mesh->faces[facecount].p[1] = ptcount + jj;
            }
            id[2] = mesh->faces[facecount].p[2] = ptcount - (ROT_PREC - 1) + j;
            id[3] = mesh->faces[facecount].p[3] = ptcount - (ROT_PREC - 1) + jj;
            
            for (k = 0; k < 4; k++) {
                POINT3D *pn = &mesh->norms[id[k]];
                
                pn->x += norm.x;
                pn->y += norm.y;
                pn->z += norm.z;
            }
            mesh->numFaces++;
            facecount++;
        }
    }

    ss_normalizeNorms(lemMesh.norms, lemMesh.numPoints);
        
    if (twistFact >= 1.0f)
        twistFactAdd = -0.01f;
    else if (twistFact <= -1.0f)
        twistFactAdd = 0.01f;
    twistFact += twistFactAdd;
        
}

BOOL initLemScene()
{
    int i;
    RGBA lightAmbient = {0.0f, 0.0f, 0.0f, 1.0f};

    iPrec = (int)(fTesselFact * 32.5);
    if (iPrec < 5)
        iPrec = 5;

    lemX = (double*)SaverAlloc(sizeof(double) * iPrec);
    if( lemX == NULL )
        return FALSE;

    lemY = (double*)SaverAlloc(sizeof(double) * iPrec);
    if( lemY == NULL )
        return FALSE;

    lemXT = (double*)SaverAlloc(sizeof(double) * iPrec);
    if( lemXT == NULL )
        return FALSE;

    lemYT = (double*)SaverAlloc(sizeof(double) * iPrec);
    if( lemYT == NULL )
        return FALSE;

/*
    D3DXMATRIX matProj;
    D3DXMatrixOrthoLH( &matProj, 3.0, 3.0, 0.0f, 3.0f );
    m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
*/
    SetProjectionMatrixInfo( TRUE, 3.0f, 3.0f, 0.0f, 3.0f );

    D3DXMATRIX matView;
    D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
    D3DXVECTOR3 vEyePt(0, 0, 1.5f);
    D3DXVECTOR3 vLookatPt(0, 0, 0);
    D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
    m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

    if (!newMesh(&lemMesh, (ROT_PREC - 1) * (iPrec - 1) , 
            (ROT_PREC - 1) * (iPrec - 1)) )
    {
        return FALSE;
    }

    for (i = 0; i < ROT_PREC; i++) {
        basis[i].x = 0.0f;
        basis[i].y = (float) (0.15 * cos((i * 2.0 * PI) / (ROT_PREC - 1.0)));
        basis[i].z = (float) (0.15 * sin((i * 2.0 * PI) / (ROT_PREC - 1.0)));
    }

    initLemCoords(iPrec);
/*
    glFrontFace(GL_CW);
    glEnable(GL_CULL_FACE);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (FLOAT *) &lightAmbient);
*/
    m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );
    myglMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
                 (FLOAT *) &Material[3].Diffuse);

    return TRUE;
}

void delLemScene()
{
    delMesh(&lemMesh);

    SaverFree(lemX);
    SaverFree(lemY);
    SaverFree(lemXT);
    SaverFree(lemYT);
}

void updateLemScene(int flags, FLOAT fElapsedTime)
{
    static double mxrot = 0.0;
    static double myrot = 0.0;
    static double mzrot = 0.0;
    static double mxrotInc = 0.0;
    static double myrotInc = 0.1;
    static double zrotInc = 0.1;
    static double mzrotInc = 0.0;
    RGBA color;
    MATRIX model;
    D3DXMATRIX mat1, mat2, mat3, mat4, mat5, matFinal;
    static FLOAT fH = 0.0f;
    FLOAT fTimeFactor = fElapsedTime * 20.0f;
    if( fTimeFactor > 0.25f )
        fTimeFactor = 0.25f;
    
    mxrot += mxrotInc * fTimeFactor;
    myrot += myrotInc * fTimeFactor;
    mzrot += mzrotInc * fTimeFactor;

    if( gbBounce ) {
        // floating window bounced off an edge
        if (mxrotInc) {
            mxrotInc = 0.0;
            myrotInc = 0.1;
        } else if (myrotInc) {
            myrotInc = 0.0;
            mzrotInc = 0.1;
        } else if (mzrotInc) {
            mzrotInc = 0.0;
            mxrotInc = 0.1;
        }
        gbBounce = FALSE;
    }

    zrot += zrotInc * fTimeFactor;
    if (zrot >= PI / 4.0) {
        zrot = PI / 4.0;
        zrotInc = -0.03;
    } else if (zrot <= -PI / 4.0) {
        zrot = -PI / 4.0;
        zrotInc = 0.03;
    }

    genLemniscate();

    if (bColorCycle) {
        ss_HsvToRgb(fH, 1.0f, 1.0f, &color );

        myglMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, (FLOAT *) &color);

        fH += fTimeFactor;
        if( fH >= 360.0f )
            fH -= 360.0f;
    }
/*
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.5, 1.5, -1.5, 1.5, 0.0, 3.0);
    glTranslatef(0.0f, 0.0f, -1.5f);
*/
/*
    glRotatef((FLOAT) (zrot * (180.0 / PI)), 0.0f, 1.0f, 0.0f);
    glRotatef(50.0f, 1.0f, 0.0f, 0.0f);
    glRotatef(50.0f, 0.0f, 0.0f, 1.0f);
*/

    D3DXMATRIX matView;
    D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
    D3DXVECTOR3 vEyePt(0, 0, 1.5f);
    D3DXVECTOR3 vLookatPt(0, 0, 0);
    D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
    m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
/*
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0f, -0.5f, 0.0f);
    glRotatef((FLOAT) (mxrot * (180.0 / PI)), 1.0f, 0.0f, 0.0f);
    glRotatef((FLOAT) (myrot * (180.0 / PI)), 0.0f, 1.0f, 0.0f);
    glRotatef((FLOAT) (mzrot * (180.0 / PI)), 0.0f, 0.0f, 1.0f);
*/
    D3DXMatrixTranslation(&mat1, 0.0f, -0.5f, 0.0f);
    D3DXMatrixRotationX(&mat2, D3DXToRadian((FLOAT)(mxrot * (180.0 / PI))));
    D3DXMatrixRotationY(&mat3, D3DXToRadian((FLOAT)(myrot * (180.0 / PI))));
    D3DXMatrixRotationZ(&mat4, D3DXToRadian((FLOAT)(mzrot * (180.0 / PI))));
    matFinal = mat4 * mat3 * mat2 * mat1 ;
    m_pd3dDevice->SetTransform( D3DTS_WORLD , &matFinal );

    ss_matrixIdent(&model);
    ss_matrixRotate(&model, mxrot, myrot, mzrot);
    ss_matrixTranslate(&model, 0.0, -0.5, 0.0);

//    updateObject(&lemMesh, bSmoothShading);    
    RenderMesh3(&lemMesh, bSmoothShading);
}