#include "qrad.h"

static dplane_t backplanes[MAX_INTERNAL_MAP_PLANES];

dleaf_t*        PointInLeaf(const vec3_t point)
{
    int             nodenum;
    vec_t           dist;
    dnode_t*        node;
    dplane_t*       plane;

    nodenum = 0;
    while (nodenum >= 0)
    {
        node = &g_dnodes[nodenum];
        plane = &g_dplanes[node->planenum];
        dist = DotProduct(point, plane->normal) - plane->dist;
        if (dist >= 0.0)
        {
            nodenum = node->children[0];
        }
        else
        {
            nodenum = node->children[1];
        }
    }

    return &g_dleafs[-nodenum - 1];
}

/*
 * ==============
 * PatchPlaneDist
 * Fixes up patch planes for brush models with an origin brush
 * ==============
 */
vec_t           PatchPlaneDist(const patch_t* const patch)
{
    const dplane_t* plane = getPlaneFromFaceNumber(patch->faceNumber);

    return plane->dist + DotProduct(g_face_offset[patch->faceNumber], plane->normal);
}

void            MakeBackplanes()
{
    int             i;

    for (i = 0; i < g_numplanes; i++)
    {
        backplanes[i].dist = -g_dplanes[i].dist;
        VectorSubtract(vec3_origin, g_dplanes[i].normal, backplanes[i].normal);
    }
}

const dplane_t* getPlaneFromFace(const dface_t* const face)
{
    if (!face)
    {
        Error("getPlaneFromFace() face was NULL\n");
    }

    if (face->side)
    {
        return &backplanes[face->planenum];
    }
    else
    {
        return &g_dplanes[face->planenum];
    }
}

const dplane_t* getPlaneFromFaceNumber(const unsigned int faceNumber)
{
    dface_t*        face = &g_dfaces[faceNumber];

    if (face->side)
    {
        return &backplanes[face->planenum];
    }
    else
    {
        return &g_dplanes[face->planenum];
    }
}

// Returns plane adjusted for face offset (for origin brushes, primarily used in the opaque code)
void getAdjustedPlaneFromFaceNumber(unsigned int faceNumber, dplane_t* plane)
{
    dface_t*        face = &g_dfaces[faceNumber];
    const vec_t*    face_offset = g_face_offset[faceNumber];

    plane->type = (planetypes)0;
    
    if (face->side)
    {
        vec_t dist;

        VectorCopy(backplanes[face->planenum].normal, plane->normal);
        dist = DotProduct(plane->normal, face_offset);
        plane->dist = backplanes[face->planenum].dist + dist;
    }
    else
    {
        vec_t dist;

        VectorCopy(g_dplanes[face->planenum].normal, plane->normal);
        dist = DotProduct(plane->normal, face_offset);
        plane->dist = g_dplanes[face->planenum].dist + dist;
    }
}

// Will modify the plane with the new dist
void            TranslatePlane(dplane_t* plane, const vec_t* delta)
{
#ifdef HLRAD_FASTMATH
	plane->dist += DotProduct(plane->normal,delta);
#else
    vec3_t          proj;
    vec_t           magnitude;

    ProjectionPoint(delta, plane->normal, proj);
    magnitude = VectorLength(proj);

    if (DotProduct(plane->normal, delta) > 0)              //if zero, magnitude will be zero.
    {
        plane->dist += magnitude;
    }
    else
    {
        plane->dist -= magnitude;
    }
#endif
}

// HuntForWorld will never return CONTENTS_SKY or CONTENTS_SOLID leafs
dleaf_t*        HuntForWorld(vec_t* point, const vec_t* plane_offset, const dplane_t* plane, int hunt_size, vec_t hunt_scale, vec_t hunt_offset)
{
    dleaf_t*        leaf;
    int             x, y, z;
    int             a;

    vec3_t          current_point;
    vec3_t          original_point;

    vec3_t          best_point;
    dleaf_t*        best_leaf = NULL;
    vec_t           best_dist = 99999999.0;

    vec3_t          scales;

    dplane_t        new_plane = *plane;

    if (hunt_scale < 0.1)
    {
        hunt_scale = 0.1;
    }

    scales[0] = 0.0;
    scales[1] = -hunt_scale;
    scales[2] = hunt_scale;

    VectorCopy(point, best_point);
    VectorCopy(point, original_point);

    TranslatePlane(&new_plane, plane_offset);

    if (!hunt_size)
    {
        hunt_size = DEFAULT_HUNT_SIZE;
    }

    for (a = 1; a < hunt_size; a++)
    {
        for (x = 0; x < 3; x++)
        {
            current_point[0] = original_point[0] + (scales[x % 3] * a);
            for (y = 0; y < 3; y++)
            {
                current_point[1] = original_point[1] + (scales[y % 3] * a);
                for (z = 0; z < 3; z++)
                {
                    vec3_t          delta;
                    vec_t           dist;

                    current_point[2] = original_point[2] + (scales[z % 3] * a);
                    SnapToPlane(&new_plane, current_point, hunt_offset);
                    VectorSubtract(current_point, original_point, delta);
                    dist = VectorLength(delta);

                    if (dist < best_dist)
                    {
                        if ((leaf = PointInLeaf(current_point)) != g_dleafs)
                        {
                            if ((leaf->contents != CONTENTS_SKY) && (leaf->contents != CONTENTS_SOLID))
                            {
                                if (x || y || z)
                                {
                                    //dist = best_dist;
                                    best_leaf = leaf;
                                    VectorCopy(current_point, best_point);
                                    continue;
                                }
                                else
                                {
                                    VectorCopy(current_point, point);
                                    return leaf;
                                }
                            }
                        }
                    }
                }
            }
        }
        if (best_leaf)
        {
            break;
        }
    }

    VectorCopy(best_point, point);
    return best_leaf;
}
