/*
MainControl.cpp
Written by Matthew Fisher

MainControl includes everything that changes often between applications, such as what meshes to load,
and also determines what is rendered each frame.

See the appropriate header file for a description of what this class does. 
*/

//All source files include Main.h
#include "Main.h"

const float SwirlyRadius = 1.0f;        //radius of the main spiral
const float SwirlyDensity = 8.0f;       //density of the rings on the main spiral
const float Epsilon = 1e-4f;            //"small Delta T" used to approximate derivatives
Vec3 SwirlyFunction(float t)
{
    //this is the equation of a spiral along the z-axis
    return Vec3(cosf(t*SwirlyDensity)*SwirlyRadius,sinf(t*SwirlyDensity)*SwirlyRadius, t);
}

Vec3 GetCurveTangent(float t)
{
    //f'(x) = (f(x+DeltaX) - f(x)) / DeltaX.  We only care about the magnitude, so the DeltaX is not important
    Vec3 Diff = SwirlyFunction(t + Epsilon) - SwirlyFunction(t);
    Diff.Normalize();
    return Diff;
}

Vec3 GetCurveNormal(float t)
{
    //the normal vector is the second derivative along the curve
    Vec3 Diff = GetCurveTangent(t + Epsilon) - GetCurveTangent(t);
    Diff.Normalize();
    return Diff;
}

void MainControl::InitCentralMesh(GraphicsDevice &GD)
{
    CentralMesh.SetGD(GD);  //associate the mesh with the graphics device

    //this create cylinder function takes an arbitrary curve in 3-space paramaterized by a variable, say t,
    //and creates a "tube" (bent cylinder) that follows the given curve from t = start to t = end.
    //the radius is the radius of the cylinder.  Note that the number of stacks needs to be very high
    //to get a good approximation of the curve.
    CentralMesh.CreateCylinder(SwirlyFunction, -4.0f, 4.0f, 0.05f, 10, 700);
    CentralMesh.ColorNormals();     //add some color
}

void MainControl::ReInit(GraphicsDevice &GD, WindowManager &WM)
{
    if(GD.GetFullScreen()) C[0].Mouse(0.0f);    //move the mouse to the middle of the screen

    MC[0].Perspective.PerspectiveFov(40.0f * PIf / 180.0f,                          //the field of view = 60 degrees
                                    float(WM.GetWidth()) / float(WM.GetHeight()),   //the aspect ratio
                                    0.5f,                                           //near Z-plane
                                    20.0f);                                         //far Z-plane

    MC[1].Perspective.PerspectiveFov(40.0f * PIf / 180.0f,                          //the field of view = 60 degrees
                                    float(WM.GetWidth()) / float(WM.GetHeight()),   //the aspect ratio
                                    0.1f,                                           //near Z-plane
                                    5.0f);                                          //far Z-plane (much closer)
}

void MainControl::Init(GraphicsDevice &GD, WindowManager &WM)
{
    ReInit(GD, WM);
    GD.DisableWireframe();

    //initalize the cameras
    C[0].Reset(eX*8.0f,     //the eye vector
            eY,             //the up vector
            Origin);        //the vector we're looking at

    C[1].Reset(Origin,      //the eye vector
            eY,             //the up vector
            eZ);            //the vector we're looking at
    CurCamera = 0;

    int i;
    for(i=0;i<2;i++)
    {
        MC[i].World.Identity(); //zero the world and view matrices
        MC[i].View.Identity();
    }

    Ind.Init(GD, 2, 15, 15);    //initalize the indicator

    CurvePos = -2.0f;       //Starting position along the curve
    InitCentralMesh(GD);

    Time = 0.0f;            //reset the time to 0
}

void MainControl::Render(GraphicsDevice &GD, WindowManager &WM)
{
    Time += T.SPF();                                //advance the current time
    if(KeyCheckOnce(KEY_F)) GD.ToggleWireframe();   //toggle wireframe if the user presses "F"

    MC[CurCamera].ResetWorldMatrix();                       //the Indicator class changes the world matrix; reset it
    C[CurCamera].WindowKeyboard(1.0f,1.0f);                 //move based upon the keyboard
    if(GD.GetFullScreen()) C[CurCamera].Mouse(0.001f);      //move based upon the mouse, if we're in full-screen mode
    C[CurCamera].Update(MC[CurCamera], GD);                 //update the camera and load it into the current graphics device

    CentralMesh.Render();                           //render the spiral

    int OtherCamera = (CurCamera + 1) % 2;          //get the other camera's index

    //this function takes a camera and draws it in another camera's frame.  It will draw the viewing frustrum, eye vector
    //and ortho-normal basis starting at the eye vector.  The matrix controller is the frame we want to render the camera in.
    //the perspective matrix is the perspective matrix of the camera we're rendering.  The radius is the radius of the sphere
    //at the camera's eye vector, and the length is the length of the orthonormal vectors.
    Ind.DrawCamera(GD, MC[CurCamera], MC[OtherCamera].Perspective, 0.01f, 0.5f, C[OtherCamera]);

    //we're going to draw the point along the curve determined by the paramaterization value CurvePos,
    //and the tangent, normal, and torsion vectors at that point.
    Vec3 CurveVector = SwirlyFunction(CurvePos);    //the position along the curve
    Vec3 CurveTangent = GetCurveTangent(CurvePos);  //the tangent vector
    Vec3 CurveNormal = GetCurveNormal(CurvePos);    //the normal vector
    Vec3 CurveTorsion;
    Vec3Cross(CurveTorsion, CurveTangent, CurveNormal); //the torsion vector is the cross product of the tangent and normal vectors
    CurveTorsion.Normalize();

    Ind.RenderSphere(GD, MC[CurCamera], 0.1f, CurveVector, White);  //render the eye vector
    Ind.RenderCylinder(GD, MC[CurCamera], 0.05f, CurveVector, CurveVector + CurveTangent, Red);     //render the tangent, normal, and torsion vectors
    Ind.RenderCylinder(GD, MC[CurCamera], 0.05f, CurveVector, CurveVector + CurveNormal, Yellow);   //centered at the point along the curve (CurveVector.)
    Ind.RenderCylinder(GD, MC[CurCamera], 0.05f, CurveVector, CurveVector + CurveTorsion, Blue);

    //update CurvePos based upon keyboard input
    if(KeyCheck(KEY_NUMPADADD)) CurvePos += T.SPF();
    if(KeyCheck(KEY_NUMPADSUBTRACT)) CurvePos -= T.SPF();

    //switch between cameras if they press tab
    if(KeyCheckOnce(KEY_TAB)) CurCamera = (CurCamera + 1) % 2;

    if(KeyCheckOnce(KEY_P))
    {
        Bitmap B;
        GD.CaptureScreen(WM, B);    //get the screen bitmap
        B.SaveBMP(keybitmap);       //save it
    }

    /*GD.DrawVector("Camera - > ", C[CurCamera].VecEye, 0, 20); //draw the camera position as text on the screen
    GD.DrawText("Use the + or - keys to change the curve position.", " ", 0, 40);   //help
    GD.DrawText("Press P to capture the screen to keys.bmp.", " ", 0, 60);          //help
    GD.DrawText("Use tab to switch cameras.", " ", 0, 80);  //help
    GD.DrawDouble("Curve Position -> ", CurvePos, 0, 100);  //draw the current curve position
    GD.DrawNumber("Camera -> ", CurCamera, 0, 120);         //draw the current camera*/
}

void MainControl::FreeMemory()
{

}