/******************************Module*Header*******************************\
* Module Name: genwin2.c
*
* The new Windows style of the 3D Flying Objects screen saver.
*
* Texture maps .BMP files onto a simulation of a flag waving in the breeze.
*
* Copyright (c) 2001 Microsoft Corporation
*
\**************************************************************************/

#include <stdlib.h>
#include <windows.h>
#include <string.h>
#include <math.h>
#include <d3dx8.h>
#include "D3DSaver.h"
#include "FlyingObjects.h"
#include "resource.h"
#include "mesh.h"


enum STATE
{
    S_FREE,
    S_MOVETOORIGIN,
    S_FADETOCOLOR,
    S_PAUSE,
    S_FADEFROMCOLOR
};

#define TIME_FREE 10.0f
#define TIME_FADETOCOLOR 1.0f
#define TIME_PAUSE 5.0f
#define TIME_FADEFROMCOLOR 1.0f
// Note: There's no TIME_MOVETOORIGIN since that takes a variable amt of time.

const FLOAT winTotalwidth = (FLOAT)0.75;
const FLOAT winTotalheight = (FLOAT)0.75;

#define MAX_FRAMES 20

// IPREC is the number of faces in the mesh that models the flag.
#define IPREC   35

static int Frames = 10;
static MESH winMesh[MAX_FRAMES];
static FLOAT sinAngle = (FLOAT)0.0;
static FLOAT xTrans = (FLOAT)0.0;

// Material properties
static RGBA matlBrightSpecular = {1.0f, 1.0f, 1.0f, 1.0f};

// Lighting properties
static FLOAT light0Pos[] = {-15.0f, 0.0f, -10.0f};

                           
                           
/******************************Public*Routine******************************\
* iPtInList
*
* Add a vertex and its normal to the mesh.  If the vertex already exists,
* add in the normal to the existing normal (we to accumulate the average
* normal at each vertex).  Normalization of the normals is the
* responsibility of the caller.
*
\**************************************************************************/
static int iPtInList(MESH *mesh, int start, 
                     POINT3D *p, POINT3D *norm, BOOL blend)
{
    int i;
    POINT3D *pts = mesh->pts + start;

    if (blend) {
        for (i = start; i < mesh->numPoints; i++, pts++) {
            if ((pts->x == p->x) && (pts->y == p->y) && (pts->z == p->z)) {
                mesh->norms[i].x += norm->x;
                mesh->norms[i].y += norm->y;
                mesh->norms[i].z += norm->z;
                return i;
            }
        }
    } else {
        i = mesh->numPoints;
    }

    mesh->pts[i] = *p;
    mesh->norms[i] = *norm;
    mesh->numPoints++;
    return i;
}




/******************************Public*Routine******************************\
* getZpos
*
* Get the z-position (depth) of the "wavy" flag component at the given x.
*
* The function used to model the wave is:
*
*        1/2
*   z = x    * sin((2*PI*x + sinAngle) / 8)
*
* The shape of the wave varies from frame to frame by changing the
* phase, sinAngle.
*
\**************************************************************************/
static FLOAT getZpos(FLOAT x)
{
    FLOAT xAbs = x - xTrans;
    FLOAT angle = sinAngle + ((FLOAT) (2.0 * PI) * (xAbs / winTotalwidth));

    xAbs = winTotalwidth - xAbs;

    return (FLOAT)(-(sin((double)angle) / 8.0) *
                   sqrt((double)(xAbs / winTotalwidth )));
}




/******************************Public*Routine******************************\
* genTex
*
* Generate a mesh representing a frame of the flag.  The phase, sinAngle,
* is a global variable.
*
\**************************************************************************/
static BOOL genTex(MESH *winMesh)
{
    POINT3D pos;
    POINT3D pts[4];
    FLOAT w, h;
    int i;

    if( !newMesh(winMesh, IPREC * IPREC, IPREC * IPREC) )
        return FALSE;

    // Width and height of each face
    w = (winTotalwidth) / (FLOAT)(IPREC + 1);
    h = winTotalheight;

    // Generate the mesh data.  At equally spaced intervals along the x-axis,
    // we compute the z-position of the flag surface.

    pos.y = (FLOAT) 0.0;
    pos.z = (FLOAT) 0.0;

    for (i = 0, pos.x = xTrans; i < IPREC; i++, pos.x += w) {
        int faceCount = winMesh->numFaces;

        pts[0].x = (FLOAT)pos.x; 
        pts[0].y = (FLOAT)(pos.y);   
        pts[0].z = getZpos(pos.x);

        pts[1].x = (FLOAT)pos.x;
        pts[1].y = (FLOAT)(pos.y + h);  
        pts[1].z = getZpos(pos.x);

        pts[2].x = (FLOAT)(pos.x + w);  
        pts[2].y = (FLOAT)(pos.y);  
        pts[2].z = getZpos(pos.x + w);

        pts[3].x = (FLOAT)(pos.x + w);
        pts[3].y = (FLOAT)(pos.y + h);
        pts[3].z = getZpos(pos.x + w);

        // Compute the face normal.
        ss_calcNorm(&winMesh->faces[faceCount].norm, pts + 2, pts + 1, pts);

        // Add the face to the mesh.
        winMesh->faces[faceCount].material = 0;
        winMesh->faces[faceCount].p[0] = iPtInList(winMesh, 0, pts,
            &winMesh->faces[faceCount].norm, TRUE);
        winMesh->faces[faceCount].p[1] = iPtInList(winMesh, 0, pts + 1,
            &winMesh->faces[faceCount].norm, TRUE);
        winMesh->faces[faceCount].p[2] = iPtInList(winMesh, 0, pts + 2,
            &winMesh->faces[faceCount].norm, TRUE);
        winMesh->faces[faceCount].p[3] = iPtInList(winMesh, 0, pts + 3,
            &winMesh->faces[faceCount].norm, TRUE);

        winMesh->numFaces++;
    }

    // Normalize the vertex normals in the mesh.
    ss_normalizeNorms(winMesh->norms, winMesh->numPoints);

    return TRUE;
}




/******************************Public*Routine******************************\
* initWin2Scene
*
* Initialize the screen saver.
*
* This function is exported to the main module in ss3dfo.c.
*
\**************************************************************************/
BOOL initWin2Scene()
{
    int i;
    FLOAT angleDelta;

    SetProjectionMatrixInfo( TRUE, 2.0f, 2.0f, 0.0f, 3.0f );

    D3DXMATRIX matView;
    D3DXMatrixTranslation(&matView, -0.17f, -0.04f, 1.5f);
    m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

    // Adjust position of light 0
    D3DLIGHT8 light;
    m_pd3dDevice->GetLight(0, &light);
    light.Position.x = light0Pos[0];
    light.Position.y = light0Pos[1];
    light.Position.z = light0Pos[2];
    m_pd3dDevice->SetLight(0, &light);

    m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );
    m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE);
    m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP );
    
    Frames = MAX_FRAMES;

    // Generate the geometry data (stored in the array of mesh structures),
    // for each frame of the animation.  The shape of the flag is varied by
    // changing the global variable sinAngle.
    angleDelta = (FLOAT)(2.0 * PI) / (FLOAT)Frames;
    sinAngle = (FLOAT) 0.0;
    for (i = 0; i < Frames; i++) {
        if( !genTex(&winMesh[i]) )
            return FALSE;
        sinAngle += angleDelta;
    }

    return TRUE;
}




/******************************Public*Routine******************************\
* delWin2Scene
*
* Cleanup the data associated with this screen saver.
*
* This function is exported to the main module in ss3dfo.c.
*
\**************************************************************************/
void delWin2Scene()
{
    int i;
    for (i = 0; i < Frames; i++)
        delMesh(&winMesh[i]);
}




/******************************Public*Routine******************************\
* updateWin2Scene
*
* Generate a scene by taking one of the meshes and rendering it
*
* This function is exported to the main module in ss3dfo.c.
*
\**************************************************************************/
void updateWin2Scene(int flags, FLOAT fElapsedTime)
{
    MESH *pMesh;
    static double mxrot = 40.0;
    static double myrot = 0;
    static double mzrot = -12.0;
    static int frameNum = 0;
    static FLOAT fFrameNum = (FLOAT)Frames;
    FLOAT s = 0.0f;
    FLOAT ds;
    static FLOAT s_fTime = 0.0f;
    static FLOAT s_fTimeLastChange = 0.0f;
    static FLOAT s_fTimeNextChange = TIME_FREE;
    static STATE s_state = S_FREE;
    FLOAT fBeta;
    if( fElapsedTime > 0.25f )
        fElapsedTime = 0.25f;
    FLOAT fTimeFactor = fElapsedTime * 20.0f;
    HRESULT hr;

    s_fTime += fElapsedTime;
    if( s_fTimeNextChange != -1.0f && s_fTime > s_fTimeNextChange )
    {
        // Handle state transitions
        s_fTimeLastChange = s_fTime;
        switch( s_state )
        {
        case S_FREE:
            s_state = S_MOVETOORIGIN;
            g_bMoveToOrigin = TRUE;
            s_fTimeNextChange = -1.0f;
            break;
        case S_MOVETOORIGIN:
            s_state = S_FADETOCOLOR;
            s_fTimeNextChange = s_fTime + TIME_FADETOCOLOR;
            break;
        case S_FADETOCOLOR:
            s_state = S_PAUSE;
            s_fTimeNextChange = s_fTime + TIME_PAUSE;
            break;
        case S_PAUSE:
            s_state = S_FADEFROMCOLOR;
            s_fTimeNextChange = s_fTime + TIME_FADEFROMCOLOR;
            break;
        case S_FADEFROMCOLOR:
            s_state = S_FREE;
            s_fTimeNextChange = s_fTime + TIME_FREE;
            g_bMoveToOrigin = FALSE;
            break;
        }
    }

    fBeta = 0.0f;

    // Handle state processing
    switch( s_state )
    {
    case S_MOVETOORIGIN:
        if( g_bAtOrigin && frameNum == 0)
            s_fTimeNextChange = s_fTime; // provoke state change next time
        break;
    case S_FADETOCOLOR:
        fBeta = (s_fTime - s_fTimeLastChange) / TIME_FADETOCOLOR;
        break;
    case S_PAUSE:
        fBeta = 1.0f;
        break;
    case S_FADEFROMCOLOR:
        fBeta = 1.0f - ( (s_fTime - s_fTimeLastChange) / TIME_FADEFROMCOLOR );
        break;
    }

    if( fBeta != 0.0f )
    {
        // Render background logo
        MYVERTEX3 v[4];
        FLOAT fLeft = g_pFloatRect->xMin - g_xScreenOrigin;
        FLOAT fRight = fLeft + g_pFloatRect->xSize;
        FLOAT fBottom = g_pFloatRect->yMin - g_yScreenOrigin;
        FLOAT fTop = g_pFloatRect->yMin + g_pFloatRect->ySize;
        DWORD dwColor = D3DXCOLOR( 1.0f, 1.0f, 1.0f, fBeta );
        v[0].p = D3DXVECTOR3(fLeft, fBottom, 0.9f); v[0].rhw = 0.1f; v[0].dwDiffuse = dwColor; v[0].tu = 0.0f; v[0].tv = 0.0f;
        v[1].p = D3DXVECTOR3(fRight, fBottom, 0.9f); v[1].rhw = 0.1f; v[1].dwDiffuse = dwColor; v[1].tu = 1.0f; v[1].tv = 0.0f;
        v[2].p = D3DXVECTOR3(fLeft, fTop, 0.9f); v[2].rhw = 0.1f; v[2].dwDiffuse = dwColor; v[2].tu = 0.0f; v[2].tv = 1.0f;
        v[3].p = D3DXVECTOR3(fRight, fTop, 0.9f); v[3].rhw = 0.1f; v[3].dwDiffuse = dwColor; v[3].tu = 1.0f; v[3].tv = 1.0f;

        hr = m_pd3dDevice->SetTexture( 0, g_pDeviceObjects->m_pTexture2 );
        hr = m_pd3dDevice->SetVertexShader( D3DFVF_MYVERTEX3 );
        hr = m_pd3dDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, v, sizeof(MYVERTEX3) );
    }
    m_pd3dDevice->SetTexture( 0, g_pDeviceObjects->m_pTexture );


    D3DXMATRIX mat1, mat2, mat3, mat4, matFinal;
    D3DXMatrixRotationX(&mat1, D3DXToRadian((FLOAT)mxrot));
    D3DXMatrixRotationY(&mat2, D3DXToRadian((FLOAT)myrot));
    D3DXMatrixRotationZ(&mat3, D3DXToRadian((FLOAT)mzrot));
    D3DXMatrixScaling( &mat4, 0.82f, 0.92f, 0.82f );
    matFinal = mat4 * mat3 * mat2 * mat1 ;
    m_pd3dDevice->SetTransform( D3DTS_WORLD, &matFinal );
    
    // Divide the texture into IPREC slices.  ds is the texture coordinate
    // delta we apply as we move along the x-axis.
    ds = (FLOAT)1.0 / (FLOAT)IPREC;

    // Setup the material property of the flag.  The material property, light
    // properties, and polygon orientation will interact with the texture.
    myglMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (FLOAT *) &matlBrightSpecular);
    myglMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, (FLOAT) 40.0);

    FLOAT fColor[4];
    fColor[0] = 1.0f;
    fColor[1] = 1.0f;
    fColor[2] = 1.0f;
    fColor[3] = 1.0f - fBeta; // Adjust flag alpha so it fades when showing logo
    if( fColor[3] != 0.0f )
    {
        // Render flag
        myglMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, fColor);
        pMesh = &winMesh[frameNum];

        INT numPrims = 0;
        INT numIndices = 0;
        INT numVertices = 0;
        WORD iVertexA, iVertexB, iVertexC, iVertexD;
        INT a,b,c,d;
        MFACE *faces;

        WORD* i;
        MYVERTEX2* v;
        hr = m_pVB2->Lock( 0, 0, (BYTE**)&v, 0 );
        hr = m_pIB->Lock( 0, MAX_INDICES, (BYTE**)&i, 0 );

        faces = pMesh->faces;
        for( int iFace = 0; iFace < pMesh->numFaces; iFace++ )
        {
            a = faces[iFace].p[0];
            b = faces[iFace].p[1];
            c = faces[iFace].p[2];
            d = faces[iFace].p[3];

            v[numVertices].p = pMesh->pts[a];
            v[numVertices].n = bSmoothShading ? -pMesh->norms[a] : -faces[iFace].norm;
            v[numVertices].tu = s; v[numVertices].tv = 1.0f;
            iVertexA = numVertices++;
            v[numVertices].p = pMesh->pts[b];
            v[numVertices].n = bSmoothShading ? -pMesh->norms[b] : -faces[iFace].norm;
            v[numVertices].tu = s; v[numVertices].tv = 0.0f;
            iVertexB = numVertices++;
            v[numVertices].p = pMesh->pts[c];
            v[numVertices].n = bSmoothShading ? -pMesh->norms[c] : -faces[iFace].norm;
            v[numVertices].tu = s+ds; v[numVertices].tv = 1.0f;
            iVertexC = numVertices++;
            v[numVertices].p = pMesh->pts[d];
            v[numVertices].n = bSmoothShading ? -pMesh->norms[d] : -faces[iFace].norm;
            v[numVertices].tu = s+ds; v[numVertices].tv = 0.0f;
            iVertexD = numVertices++;

            s += ds;

            i[numIndices++] = iVertexA;
            i[numIndices++] = iVertexB;
            i[numIndices++] = iVertexC;
            numPrims++;
            i[numIndices++] = iVertexC;
            i[numIndices++] = iVertexB;
            i[numIndices++] = iVertexD;
            numPrims++;
        }        

        hr = m_pVB2->Unlock();
        hr = m_pIB->Unlock();

        hr = m_pd3dDevice->SetVertexShader( D3DFVF_MYVERTEX2 );
        hr = m_pd3dDevice->SetStreamSource( 0, m_pVB2, sizeof(MYVERTEX2) );
        hr = m_pd3dDevice->SetIndices( m_pIB, 0 );

        hr = m_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, numVertices, 
            0, numPrims );
    }

    // Don't change frame number if we're in S_FADETOCOLOR, S_PAUSE, 
    // or S_FADEFROMCOLOR, unless by some chance we're in those states
    // but framenum is not at zero (yet).
    if( frameNum != 0 ||
        s_state != S_FADETOCOLOR &&
        s_state != S_PAUSE && 
        s_state != S_FADEFROMCOLOR )
    {
        fFrameNum -= fTimeFactor;
        frameNum = (INT)fFrameNum;
        if (frameNum < 0)
        {
            fFrameNum = (FLOAT)(Frames - 1);
            frameNum = Frames - 1;
        }
    }
}