/*
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 int ShapeCount = 3;   //the number of shapes in the shape ring
const int CutCount = 4;     //the number of possible cuts

float CylinderRadius;           //the radius of the cylinder's cut function
float CylinderFunction(Vec3 &V) //a global function for splitting a mesh into two regions, one where this function is negative
                                //(the inside of the cylinder) and another mesh that represents the region outside the cylinder.
{
    return V.y * V.y + V.z * V.z - CylinderRadius * CylinderRadius;
}

float GridSize;                 //a paramater in the GridFunction function
float GridFunction(Vec3 &V)     //same purpose as CylinderFunction, except more complicated
{
    return float((int(fabsf(V.x/GridSize)) + int(fabsf(V.y/GridSize)) + int(fabsf(V.z/GridSize))) % 2 - 1);
}

void MainControl::InitShapes(GraphicsDevice &GD)
{
    ShapesUnCut.Allocate(ShapeCount);   //allocate room for 3 meshes

    int i;
    for(i=0;i<ShapeCount;i++)
        ShapesUnCut[i].SetGD(GD);       //associate the 3 meshes with the GD

    ShapesUnCut[0].CreateSphere(1.0f, 3);   //load a sphere
    ShapesUnCut[1].LoadObj("feline.obj");
    ShapesUnCut[2].LoadObj("bunny.obj");    //load the other two meshes from object files

    for(i=0;i<ShapeCount;i++)
    {
        ShapesUnCut[i].CenterObject();
        ShapesUnCut[i].SetRadius(1.5f);     //center the shapes
    }
}

void MainControl::CutShapes(GraphicsDevice &GD)
{
    ShapesCut.Allocate(ShapeCount);     //allocate room for 3 meshes

    Mesh Out1, Out2;                    //when the Split function is called, we'll need 2 temporary meshes
                                        //which represent the two output regions.
    Out1.SetGD(GD); Out2.SetGD(GD);     //associate these meshes with the graphics device

    int i;
    for(i=0;i<ShapeCount;i++)
    {
        ShapesCut[i].SetGD(GD);         //associate the mesh with the GD

        if(CutType == 0)                //CutType 0 = no cut
            ShapesCut[i] = ShapesUnCut[i];

        if(CutType == 1)                //CutType 1 = planar cut
        {
            Plane CutPlane;
            CutPlane.LoadPointNormal(Origin,eX * 0.2f + eY);    //create a simple plane
            ShapesUnCut[i].PerfectSplit(CutPlane, Out1, Out2);  //cut the mesh into two regions, one on
                                                                //each side of the plane.
            Out2.Translate(eZ * 0.3f + eY * 0.3f);              //displace one region slightly
            ShapesCut[i] = Out1;
            ShapesCut[i] += Out2;                               //combine the two regions as our output mesh
        }

        if(CutType == 2)                //CutType 2 = cylinder cut
        {
            CylinderRadius = 0.35f;                                     //the radius of the cylinder
            ShapesUnCut[i].PerfectSplit(CylinderFunction, Out1, Out2);  //cut the mesh as we did with the plane,
                                                                        //except now we use an arbitrary function
            ShapesCut[i] = Out2;    //only render one side
        }

        if(CutType == 3)                //CutType 3 = odd cut
        {
            GridSize = 0.25f;
            ShapesUnCut[i].PerfectSplit(GridFunction, Out1, Out2);      //same as the cylinder cut, except a different function
            ShapesCut[i] = Out2;
        }

    }

    //load the 3 shapes into a ring
    for(i=0;i<ShapeCount;i++)
    {
        float Theta = i / float(ShapeCount) * 2.0f * PIf;
        float Radius = 3.0f;                                                        //angle and radius around the ring
        ShapesCut[i].Translate(Vec3(cosf(Theta)*Radius,sinf(Theta)*Radius,0.0f));   //translate using polar coordinates
        ShapesCut[i].ColorNormals();                                                //apply some color
    }

    T.Pause();
}

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

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

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

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

    MC.World.Identity();    //zero the world and view matrices
    MC.View.Identity();

    CutType = 1;            //Start with a planar cut
    InitShapes(GD);         //initalize the shapes
    CutShapes(GD);

    TotalRotation = Vec3(1.0f, 1.0f, 1.0f); //start with some simple rotation
    CurrentVariable = 2;                    //start by rotating around the y-axis
    SecondsUntilSwitch = 3.0f;              //3 seconds until we switch the axis of rotation

    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"

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

    SecondsUntilSwitch -= T.SPF();      //decrement seconds left until we switch
    if(SecondsUntilSwitch < 0.0f)       //if we should switch this frame,
    {
        SecondsUntilSwitch = 3.0f;      //reset the switch timer
        CurrentVariable = rand() % 3;   //change the variable
    }

    if(CurrentVariable == 0) TotalRotation.x += T.SPF();
    if(CurrentVariable == 1) TotalRotation.y += T.SPF();
    if(CurrentVariable == 2) TotalRotation.z += T.SPF();    //based upon the variable, update the rotation vector

    Matrix RotationX, RotationY, RotationZ;         //the 3 rotation matrices for the ring
    RotationX.RotationX(TotalRotation.x);
    RotationY.RotationY(TotalRotation.y);
    RotationZ.RotationZ(TotalRotation.z);           //load the rotation matrices
    MC.World = RotationZ * RotationX * RotationY;   //load the product of the 3 matrices as the world matrix,
    GD.LoadMatrix(MC);                              //load this new matrix set into the graphics device

    int i;
    for(i=0;i<ShapeCount;i++)
        ShapesCut[i].Render();                          //draw all the cut shapes

    //update CutType based upon keyboard input
    if(KeyCheckOnce(KEY_NUMPADADD))
    {
        CutType++;
        if(CutType == CutCount) CutType = 0;
        CutShapes(GD);
    }

    if(KeyCheckOnce(KEY_NUMPADSUBTRACT))
    {
        CutType--;
        if(CutType < 0) CutType = CutCount - 1;
        CutShapes(GD);
    }

    GD.DrawVector("Camera - > ", C.VecEye, 0, 20);  //draw the camera position as text on the screen
    GD.DrawText("Use the + or - keys on the number pad to alter the cut type.", " ", 0, 40);    //help
    GD.DrawNumber("Cut Type -> ", CutType, 0, 60);  //draw the current cut type
}

void MainControl::FreeMemory()
{

}