/*
    ANTLand simulation
    (C) 2004 justin_sane

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    Contact: justinsanemsu@msu.edu

    FILE: world.cpp
    Authors: justin_sane
*/


#include <math.h>

#pragma hdrstop

#include "world.h"
#include "utility.h"


#pragma package(smart_init)

// gloable sine and cosine tables

float sin_table[360];
float cos_table[360];


/**********************************
** Setup and shutdoen functions  **
**********************************/


/* default world constructor */
__fastcall world_class::world_class()
{
    // Create the offscree buffers
    sim_buffer = new Graphics::TBitmap;
    vis_buffer = new Graphics::TBitmap;
}
/* end of world constructor */


/* default world destructor */
__fastcall world_class::~world_class()
{
    // destroy graphics buffers
   delete sim_buffer;
   delete vis_buffer;
}
/* end of world destructor */



/* reset the entire simulation */
void __fastcall world_class::reset( void )
{
    // Delete the old debug file
    DeleteFile("debug.txt");

    // Give a starting point
    debug("Simulation start\n");

    // Remove all of the old ants
    for (unsigned int i = 0; i < ants.size(); i++)
        delete ants[i];

    // empty the vector
    ants.clear();

    // same with obstacles
    obstacles.clear();

    // same with vertex vector
    vert.clear();

    // same with face vector
    faces.clear();

    // make 20 random obstacles
    random_obstacle(25);

    // add 10 random ants to the sim
    random_ant(10);

    // no ant is selected
    sel_ant = NULL;

    //copy the obstacles to the vertex set for visulizaton
    setup_vertex_set();

    // setup a camera position for the iso view
    camera_y_dir = -30;
    camera_dir = 45;

    camera_pos = get_point_3d(0, 0, 20);
}
/* end of reset_sim */


/* creates random ants */
void __fastcall world_class::random_ant( unsigned int num )
{
    while (num--)
        ants.push_back(new ant_class(obstacles, ants));
}
/* end of random_ant */



/* creates random obstacles */
void __fastcall world_class::random_obstacle( unsigned int num )
{
    while (num--)
        obstacles.push_back(obstacle_class(obstacles, ants));
}
/* end of random_obstacle */



/*****************************************
** End of Setup and shutdoen functions  **
*****************************************/





/**********************************
** Functions for the 3D vis area **
**********************************/



/* create the gloable vertex set for 3d display */
void __fastcall world_class::setup_vertex_set()
{
    point3d dummy;
    point d;

    vert.clear();

    const int border_tiles = 20;

    vert.resize(obstacles.size() * 8 + border_tiles * 8, dummy);  // 8 more for the sim area bounds

    for (unsigned int i = 0; i < obstacles.size(); i++)
    {
        unsigned int b = i * 8;  // base index for these vert

        float height  = rvalplus(4) + 2;

        // create the 8 points for each object
        vert[b + 0] = get_point_3d(obstacles[i].get_upper_left().x,  obstacles[i].get_upper_left().y, height);
        vert[b + 1] = get_point_3d(obstacles[i].get_upper_right().x, obstacles[i].get_upper_right().y, height);
        vert[b + 2] = get_point_3d(obstacles[i].get_lower_right().x, obstacles[i].get_lower_right().y, height);
        vert[b + 3] = get_point_3d(obstacles[i].get_lower_left().x,  obstacles[i].get_lower_left().y, height);

        vert[b + 4] = get_point_3d(obstacles[i].get_upper_left().x,  obstacles[i].get_upper_left().y, 0);
        vert[b + 5] = get_point_3d(obstacles[i].get_upper_right().x, obstacles[i].get_upper_right().y, 0);
        vert[b + 6] = get_point_3d(obstacles[i].get_lower_right().x, obstacles[i].get_lower_right().y, 0);
        vert[b + 7] = get_point_3d(obstacles[i].get_lower_left().x,  obstacles[i].get_lower_left().y, 0);

        faces.push_back(make_face(&vert[b + 0], &vert[b + 3], &vert[b + 7], &vert[b + 4], obstacles[i].get_color()));
        faces.push_back(make_face(&vert[b + 3], &vert[b + 2], &vert[b + 6], &vert[b + 7], obstacles[i].get_color()));
        faces.push_back(make_face(&vert[b + 2], &vert[b + 1], &vert[b + 5], &vert[b + 6], obstacles[i].get_color()));
        faces.push_back(make_face(&vert[b + 1], &vert[b + 0], &vert[b + 4], &vert[b + 5], obstacles[i].get_color()));
        faces.push_back(make_face(&vert[b + 0], &vert[b + 1], &vert[b + 2], &vert[b + 3], obstacles[i].get_color()));         // upper face
    }


    unsigned int base = obstacles.size() * 8;

    // create the verticies for wall along the y = 0 line
    for (int i = 0; i < border_tiles; i++)
    {
       vert[base++] = get_point_3d((sim_x_size / (float)border_tiles) * i, 0, 0);       // lower
       vert[base++] = get_point_3d((sim_x_size / (float)border_tiles) * i, 0, 4);       // upper
    }

    // x = sim_x_size line
    for (int i = 0; i < border_tiles; i++)
    {
       vert[base++] = get_point_3d(sim_x_size, (sim_y_size / (float)border_tiles) * i, 0);        // lower
       vert[base++] = get_point_3d(sim_x_size, (sim_y_size / (float)border_tiles) * i, 4);        // upper
    }

    // y = sim_y_size line
    for (int i = 0; i < border_tiles; i++)
    {
       vert[base++] = get_point_3d(sim_x_size - (sim_x_size / (float)border_tiles) * i, sim_y_size, 0);        // lower
       vert[base++] = get_point_3d(sim_x_size - (sim_x_size / (float)border_tiles) * i, sim_y_size, 4);        // upper
    }

    // y = 0 line
    for (int i = 0; i < border_tiles; i++)
    {
        vert[base++] = get_point_3d(0, sim_y_size - (sim_y_size / (float)border_tiles) * i, 0);        // lower
        vert[base++] = get_point_3d(0, sim_y_size - (sim_y_size / (float)border_tiles) * i, 4);        // upper
    }


    base = obstacles.size() * 8;

    // create the tiles
    for (int i = 0; i < border_tiles * 4; i++)
    {
        // wrap around to start of border vertices, for the last tile
        if (base == obstacles.size() * 8 + border_tiles * 8 - 2)
        { // the last one needs to swar back to the first set
            faces.push_back(make_face(&vert[base + 0], &vert[obstacles.size() * 8],
                                      &vert[obstacles.size() * 8 + 1], &vert[base + 1], random_color()));
        }
        else // normal face add
        {
            faces.push_back(make_face(&vert[base + 0], &vert[base + 2],
                                      &vert[base + 3], &vert[base + 1], random_color()));

        }
        base += 2;
    }
}
/* end of create_vertex set */



/* transform verticies into view / screen space */
void __fastcall world_class::transform_vertices(point3d p, trig_data_type td)
{
    int height = vis_height / 2;
    int width = vis_width / 2;

    for (unsigned int i = 0; i < vert.size(); i++)
    {
        // move the verticies so pos is at the center
        float x = vert[i].x - p.x;
        float y = vert[i].y - p.y;
        float z = vert[i].z - p.z;

        float new_x, new_y, new_z;

        // rotate the point arround the dir
        new_x = x * td.dir_cos - y * td.dir_sin;
        new_y = x * td.dir_sin + y * td.dir_cos;

        x = new_x; y = new_y;

        // rotate the point arround the y_dir
        new_x = x * td.y_dir_cos - z * td.y_dir_sin;
        new_z = x * td.y_dir_sin + z * td.y_dir_cos;
    	x = new_x; z = new_z;

        // copy over the rotated verticies
        vert[i].rx = x;
        vert[i].ry = y;
        vert[i].rz = z;

        // elimitate divide by zero
        if ((int)(x) == 0) x = 1;

        float x_inv = 1.0 / x;

        // project into 3d
        vert[i].sx = width  - y * vis_width * x_inv;
        vert[i].sy = height - z * vis_height * x_inv;

        vert[i].valid = x > 1; // clip x plane
    }
}
/* end of transform_vertices */


/* sort the faces back to front */
void __fastcall world_class::sort_faces(void)
{
    for (unsigned int i = 0; i < faces.size(); i++)
        faces[i].depth = (faces[i].v[0]->rx + faces[i].v[1]->rx  +
                          faces[i].v[2]->rx + faces[i].v[3]->rx ) / 4.0;

    // replace this bubble sort
    for (unsigned int i = 0; i < faces.size(); i++)
        for (unsigned int j = 0; j < faces.size() - 1; j++)
            if (faces[j].depth < faces[j + 1].depth)
            {
                face tmp = faces[j];
                faces[j] = faces[j+1];
                faces[j+1] = tmp;
            }
}
/* end of soft faces &/


/* is the face visiable (towards us) */
bool __fastcall world_class::face_towards_camera(face &f)
{
    // return true of the normal is facing us
    return ((f.v[1]->sx - f.v[0]->sx) * (f.v[2]->sy - f.v[0]->sy) >
            (f.v[2]->sx - f.v[0]->sx) * (f.v[1]->sy - f.v[0]->sy));
}
/* end ofc face_towards_camera */



/* draw all of the polygons */
void __fastcall world_class::draw_polygons(void)
{
    // draw the polygons
    for (unsigned int i = 0; i < faces.size(); i++)
        if ((faces[i].v[0]->valid && faces[i].v[1]->valid &&
             faces[i].v[2]->valid && faces[i].v[3]->valid))
            if (face_towards_camera(faces[i]))
            {
                TPoint points[4];

                // copy the screen verticies into a point array
                 for (int t = 0; t < 4; t++)
                    points[t] = Point(faces[i].v[t]->sx, faces[i].v[t]->sy);

                 vis_buffer->Canvas->Brush->Color = faces[i].color;
                 vis_buffer->Canvas->Polygon(points, 3);
            }
}
/* emd of draw polygons */


// project from a 2d level
void __fastcall world_class::draw_visarea(TPaintBox *drawarea)
{
    // is an ant selected
    if (sel_ant != NULL)
    {
        transform_vertices(get_point_3d(sel_ant->get_pos().x, sel_ant->get_pos().y, 2),
                           sel_ant->get_trig_data());
    }
    else
    {
        update_trig_data();
        transform_vertices(camera_pos, trig_data); // use camera
    }

    sort_faces();

   // clear the buffer
    vis_buffer->Canvas->Brush->Color = (TColor)RGB(120, 120, 120);
    vis_buffer->Canvas->FillRect(drawarea->ClientRect);

    draw_polygons();

    // copy the buffer to the display
    drawarea->Canvas->Draw(0,0,vis_buffer);
}


/* calculate the new trig data */
void world_class::update_trig_data( void )
{
    trig_data.dir_cos = fast_cos(-camera_dir);
    trig_data.dir_sin = fast_sin(-camera_dir);

    trig_data.y_dir_cos = fast_cos(-camera_y_dir);
    trig_data.y_dir_sin = fast_sin(-camera_y_dir);
}

/*********************************
** End of 3D vis area functions **
*********************************/










/**********************************
** Functions for the 2d SIM area **
**********************************/

void __fastcall world_class::draw_simarea(TPaintBox *drawarea)
{
    // clear the buffer
    sim_buffer->Canvas->Brush->Color = clWhite;
    sim_buffer->Canvas->FillRect(drawarea->ClientRect);

    // draw each obstacle
    for (unsigned int i = 0; i < obstacles.size(); i++)
        obstacles[i].draw(sim_buffer);

    // draw each ant
    for (unsigned int i = 0; i < ants.size(); i++)
        ants[i]->draw(sim_buffer);

    // copy the buffer to the display
    drawarea->Canvas->Draw(0,0,sim_buffer);
}


/*****************************************
** End of Functions for the 2d SIM area **
*****************************************/





/**********************
** Utility Functions **
**********************/

void __fastcall world_class::simarea_resize(int width, int height)
{
    sim_height = height;
    sim_width = width;

    // resize the sim area buffer
    sim_buffer->Width  = sim_width;
    sim_buffer->Height = sim_height;

    // calculate the new screen points for the obstacles
    for (unsigned int i = 0; i < obstacles.size(); i++)
        obstacles[i].refresh_screen_points();
}


void __fastcall world_class::visarea_resize(int width, int height)
{
    vis_width = width;
    vis_height = height;

    // resize the vis area buffer
    vis_buffer->Width  = vis_width;
    vis_buffer->Height = vis_height;
}


void __fastcall world_class::clear( void )
{
    // delete the ants
    for (unsigned int i = 0; i < ants.size(); i++)
        delete ants[i];
}


void __fastcall world_class::update( void )
{
    // update the state variables for each ant
    for (unsigned int i = 0; i < ants.size(); i++)
        ants[i]->update(obstacles, ants);
}


/*****************************
** End of Utility Functions **
*****************************/


/************************
** Collision detection **
************************/



/* returns the number of the ant that the coords are in */
int __fastcall world_class::point_in_ant_num(point p)
{
    for (unsigned int i = 0; i < ants.size(); i++)
        if (ants[i]->point_inside(p)) return(i);

    return(-1);
}
/* end of point_in_ant_num */




void world_class::check_selection(point p)
{
    int ant_num;

    // find out if we are selecting an ant
    ant_num = point_in_ant_num(p);

    // clear the old selection
    if (sel_ant && (ants[ant_num] != sel_ant))
        sel_ant->set_selected(false);

    // slect the ner ant
    if (ant_num >= 0)
    {
        sel_ant = ants[ant_num];
        sel_ant_index = ant_num;
        sel_ant->toggle_selected();
    }else
        sel_ant = NULL;
}


void world_class::move(move_type move)
{
    // move the ant, if one is selected
    if (sel_ant) sel_ant->move(move);
    else
    { // otherwise move the camera
        if (move == ang_up)
        {
            camera_y_dir += 1;
            if (camera_y_dir > 45)  camera_y_dir = 45;
        } else
        if (move == ang_down)
        {
            camera_y_dir -= 1;
            if (camera_y_dir < -45)  camera_y_dir = -45;
        } else
        if (move == turn_left)  camera_dir += 2;     else
        if (move == turn_right) camera_dir -= 2;     else
        if (move == up)         camera_pos.z += 0.2; else
        if (move == down )      camera_pos.z -= 0.2; else
        if (move == forw)
        {
            camera_pos.x += 1.0 * fast_cos(camera_dir);
            camera_pos.y += 1.0 * fast_sin(camera_dir);
        } else
        if (move == back)
        {
            camera_pos.x -= 1.0 * fast_cos(camera_dir);
            camera_pos.y -= 1.0 * fast_sin(camera_dir);
        }
        if (move == slide_left)
        {
            camera_pos.x += 0.5 * fast_cos(camera_dir + 90);
            camera_pos.y += 0.5 * fast_sin(camera_dir + 90);
        } else
        if (move == slide_right)
        {
            camera_pos.x += 0.5 * fast_cos(camera_dir - 90);
            camera_pos.y += 0.5 * fast_sin(camera_dir - 90);
        }

        if (camera_dir > 360) camera_dir -= 360;
        if (camera_dir  < 0)  camera_dir += 360;

        if (camera_pos.z < 0) camera_pos.z = 0;

        if (camera_pos.x < 4) camera_pos.x = 4;
        if (camera_pos.y < 4) camera_pos.y = 4;

        if (camera_pos.x > sim_x_size - 4) camera_pos.x = sim_x_size - 4;
        if (camera_pos.y > sim_y_size - 4) camera_pos.y = sim_y_size - 4;

    }
}



void __fastcall world_class::toggle_sel_self_control( void )
{
    if (sel_ant)
        sel_ant->toggle_self();
}


void __fastcall world_class::delete_selected( void )
{
    if (sel_ant)
        ants.erase(ants.begin() + sel_ant_index);

    sel_ant = NULL;
}



void world_class::get_sel_stat(char *stat)
{
    if (sel_ant)
        sel_ant->get_stat(stat);
    else
        strcpy(stat, "None Selected");

}
