/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  Copyright (C) 1996-1997  Id Software, Inc.

  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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "pch.h"
#include "quakedef.h"

// updated code here is thanks to mh!

vec3_t    chase_pos;
vec3_t    chase_angles;

vec3_t    chase_dest;
vec3_t    chase_dest_angles;

int     chase_alpha;
qboolean chase_nodraw;


void Chase_Init (void)
{   
    Cvar_RegisterVariable ("chase_back", "100");
    Cvar_RegisterVariable ("chase_up", "16");
    Cvar_RegisterVariable ("chase_right", "0");
    Cvar_RegisterVariable ("chase_active", "0", true);
    Cvar_RegisterVariable ("chase_scale", "2");
}

void Chase_Reset (void)
{
    // for respawning and teleporting
//    start position 12 units behind head
}

qboolean Chase_Check (vec3_t checkpoint, int viewcontents)
{
    int i;
    vec3_t mins;
    vec3_t maxs;

    // check against world model
    if ((Mod_PointInLeaf (checkpoint, cl.worldmodel))->contents != viewcontents)
        return false;

    // check visedicts - this happens *after* CL_ReadFromServer so the list will be valid
    for (i = 0; i < cl_numvisedicts; i++)
    {
        // retrieve the current entity
        entity_t *e = cl_visedicts[i];

        // don't check against self
        if (e == &cl_entities[cl.viewentity]) continue;

        // derive the bbox
        if (e->model->type == mod_brush && (e->angles[0] || e->angles[1] || e->angles[2]))
        {
            // copied from R_CullBox rotation test for inline bmodels, loop just unrolled
            mins[0] = e->origin[0] - e->model->radius;
            maxs[0] = e->origin[0] + e->model->radius;
            mins[1] = e->origin[1] - e->model->radius;
            maxs[1] = e->origin[1] + e->model->radius;
            mins[2] = e->origin[2] - e->model->radius;
            maxs[2] = e->origin[2] + e->model->radius;
        }
        else
        {
            VectorAdd (e->origin, e->model->mins, mins);
            VectorAdd (e->origin, e->model->maxs, maxs);
        }

        // check against bbox
        if (checkpoint[0] < mins[0]) continue;
        if (checkpoint[1] < mins[1]) continue;
        if (checkpoint[2] < mins[2]) continue;
        if (checkpoint[0] > maxs[0]) continue;
        if (checkpoint[1] > maxs[1]) continue;
        if (checkpoint[2] > maxs[2]) continue;

        // point inside
        return false;
    }

    // it's good now
    return true;
}

void TraceLine (vec3_t start, vec3_t end, vec3_t impact)
{
    trace_t    trace;

    memset (&trace, 0, sizeof(trace));
    SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, start, end, &trace);

    VectorCopy (trace.endpos, impact);
}

const float CHASE_DEST_OFFSET = 2.0f;

void Chase_Adjust (vec3_t chase_dest)
{
    // calculate distance between chasecam and original org to establish number of tests we need.
    // an int is good enough here.:)  add a cvar multiplier to this...
    int num_tests = sqrt ((r_refdef.origin[0] - chase_dest[0]) * (r_refdef.origin[0] - chase_dest[0]) +
        (r_refdef.origin[1] - chase_dest[1]) * (r_refdef.origin[1] - chase_dest[1]) +
        (r_refdef.origin[2] - chase_dest[2]) * (r_refdef.origin[2] - chase_dest[2])) * Cvar_VariableValue("chase_scale");

    // take the contents of the view leaf
    int viewcontents = (Mod_PointInLeaf (r_refdef.origin, cl.worldmodel))->contents;
    int best, test;

    // move along path from r_refdef.origin to chase_dest
    for (best = 0; best < num_tests; best++)
    {
        vec3_t chase_newdest;

        chase_newdest[0] = r_refdef.origin[0] + (chase_dest[0] - r_refdef.origin[0]) * best / num_tests;
        chase_newdest[1] = r_refdef.origin[1] + (chase_dest[1] - r_refdef.origin[1]) * best / num_tests;
        chase_newdest[2] = r_refdef.origin[2] + (chase_dest[2] - r_refdef.origin[2]) * best / num_tests;

        // check for a leaf hit with different contents
        if (!Chase_Check (chase_newdest, viewcontents))
        {
            // go back to the previous best as this one is bad
            if (best > 1)
                best--;
            else best = num_tests;
            break;
        }
    }

    // certain surfaces can be viewed at an oblique enough angle that they are partially clipped
    // by znear, so now we fix that too...
    int chase_vert[] = {0, 0, 1, 1, 2, 2};
    int dest_offset[] = {CHASE_DEST_OFFSET, -CHASE_DEST_OFFSET};

    // move along path from chase_dest to r_refdef.origin
    for (; best >= 0; best--)
    {
        // number of matches
        int nummatches = 0;

        // adjust
        chase_dest[0] = r_refdef.origin[0] + (chase_dest[0] - r_refdef.origin[0]) * best / num_tests;
        chase_dest[1] = r_refdef.origin[1] + (chase_dest[1] - r_refdef.origin[1]) * best / num_tests;
        chase_dest[2] = r_refdef.origin[2] + (chase_dest[2] - r_refdef.origin[2]) * best / num_tests;

        // run 6 tests: -x/+x/-y/+y/-z/+z
        for (test = 0; test < 6; test++)
        {
            // adjust, test and put back.
            chase_dest[chase_vert[test]] -= dest_offset[test & 1];
            if (Chase_Check (chase_dest, viewcontents)) nummatches++;
            chase_dest[chase_vert[test]] += dest_offset[test & 1];
        }

        // test result, if all match we're done in here
        if (nummatches == 6) break;
    }

    float chase_length = (r_refdef.origin[0] - chase_dest[0]) * (r_refdef.origin[0] - chase_dest[0]);
    chase_length += (r_refdef.origin[1] - chase_dest[1]) * (r_refdef.origin[1] - chase_dest[1]);
    chase_length += (r_refdef.origin[2] - chase_dest[2]) * (r_refdef.origin[2] - chase_dest[2]);

    if (chase_length < 150)
    {
        chase_nodraw = true;
        chase_alpha = 255;
    }
    else
    {
        chase_nodraw = false;
        chase_alpha = (chase_length - 150);
        if (chase_alpha > 255) chase_alpha = 255;
    }
}

void Chase_Update (void)
{
    int        i;
    float    dist;
    vec3_t    forward, up, right;
    vec3_t    dest, stop;

    // if can't see player, reset
    AngleVectors (cl.viewangles, forward, right, up);

    // calc exact destination
    for (i=0 ; i<3 ; i++)
    {
        chase_dest[i] = r_refdef.origin[i] - 
            forward[i] * Cvar_VariableValue("chase_back") - 
            right[i] * Cvar_VariableValue("chase_right");
    }
    chase_dest[2] = r_refdef.origin[2] + Cvar_VariableValue("chase_up");

    // don't allow really small or negative scaling values
    if (Cvar_VariableValue("chase_scale") < 0.01)
    {
       // revert to default Q1 chasecam behaviour
       // store out alpha and nodraw so that they're valid
       chase_alpha = 255;
       chase_nodraw = false;
    }
    else
    {
       // adjust the chasecam to prevent it going into solid
       Chase_Adjust (chase_dest);
    }

    // store alpha to entity
    cl_entities[cl.viewentity].alphaval = chase_alpha / 255.0f;

    // find the spot the player is looking at
    VectorMA (r_refdef.origin, 4096, forward, dest);
    TraceLine (r_refdef.origin, dest, stop);

    // calculate pitch to look at the same spot from camera
    VectorSubtract (stop, r_refdef.origin, stop);
    dist = DotProduct (stop, forward);
    if (dist < 1)
        dist = 1;
    r_refdef.viewangles[PITCH] = -atan(stop[2] / dist) / M_PI * 180;

    // move towards destination
    VectorCopy (chase_dest, r_refdef.origin);
}

