#include "qrad.h"

#define NUM_LERP_POINTS 4 //used by PointInWall (defines magic number)

//===============================================
// PointInEdge - base for all boundary checks
//===============================================

inline bool PointInEdge(const vec_t* const point, const vec_t* const p1, const vec_t* const p2, const vec_t* const normal)
{
	vec3_t edge, temp, temp2; //don't make static because we need multithread support
	VectorSubtract(p1,p2,edge);
	VectorSubtract(p1,point,temp);
	CrossProduct(edge,temp,temp2);
	//old code normalizes here, which is useless when detecting sign change below
	return (DotProduct(temp2,normal) >= 0);
}

//===============================================
// PointInWall - check a lerpWall_t boundary
//===============================================

inline bool PointInWall(const lerpWall_t* const wall, const vec_t* const point)
{
	for(int counter = 0; counter < NUM_LERP_POINTS; counter++)
	{
		if(!PointInEdge(point,wall->vertex[counter],wall->vertex[(counter+1)%4],wall->plane.normal))
		{ return false; }
	}
	return true;
}

//===============================================
// PointInWall - check a Winding boundary
//===============================================

inline bool PointInWinding(const Winding* const W, const dplane_t* const plane, const vec_t* const point)
{
	//reverse direction of points because plane input is 180 degrees from desired normal
	for(size_t counter = 0; counter < W->m_NumPoints-1; counter++)
	{
		if(!PointInEdge(point,W->m_Points[counter+1],W->m_Points[counter],plane->normal))
		{ return false; }
	}
	return PointInEdge(point,W->m_Points[0],W->m_Points[W->m_NumPoints-1],plane->normal);
}

//==================================================
// PointInTri - check a boundary defined by 3 points
//==================================================

inline bool PointInTri(const vec_t* const point, const dplane_t* const plane, const vec_t* const p1, const vec_t* const p2, const vec_t* const p3)
{
	return (PointInEdge(point,p1,p2,plane->normal) && PointInEdge(point,p2,p3,plane->normal) && PointInEdge(point,p3,p1,plane->normal));
}

//==================================================
// LineSegmentIntersectsBounds - does the line pass through the box?
//==================================================

inline bool LineSegmentIntersectsBounds(const vec_t* const p1, const vec_t* const p2, const vec3_t& mins, const vec3_t& maxs)
{
	vec_t tNear = -999999999;
	vec_t tFar  =  999999999;
	vec_t t0,t1,tmp;

	vec3_t d;
	VectorSubtract(p2,p1,d);

	for(int index = 0; index < 3; ++index)
	{
		if(fabs(d[index]) < EQUAL_EPSILON)
		{
			if(p1[index] < (mins[index] - EQUAL_EPSILON) || p1[index] > (maxs[index] + EQUAL_EPSILON))
			{
				return false;
			}
			continue;
		}

		t0 = (mins[index] - p1[index]) / d[index];
		t1 = (maxs[index] - p1[index]) / d[index];

		if(t0 > t1)
		{
			tmp = t1;
			t1 = t0;
			t0 = tmp;
		}

		if(t0 > tNear)
		{
			tNear = t0;
		}

		if(t1 < tFar)
		{
			tFar = t1;
		}

		if(tNear > tFar || tFar < 0)
		{
			return false;
		}
	}

	return true;
}

//==================================================
// LineSegmentIntersectsFace - does the face block the segment?
//==================================================

inline bool LineSegmentIntersectsFace(const vec_t* const p1, const vec_t* const p2, vec3_t& point_out, const int index)
{
	if(LineSegmentIntersectsBounds(p1,p2,g_opaque_face_list[index].mins,g_opaque_face_list[index].maxs))
	{
		if(LineSegmentIntersectsPlane(g_opaque_face_list[index].plane,p1,p2,point_out))
		{
			if(PointInWinding(g_opaque_face_list[index].winding,&g_opaque_face_list[index].plane,point_out))
			{
				return true;
			}
		}
	}
	return false;
}

//==================================================
// LineSegmentIntersectsPlane - returns intersection
// point in point parameter if it exists
//==================================================

inline bool LineSegmentIntersectsPlane(const dplane_t& plane, const vec_t* const p1, const vec_t* const p2, vec3_t& point)
{
	vec3_t line;
	VectorSubtract(p2,p1,line);
	vec_t dist1 = DotProduct(plane.normal,line); //p1 to p2

	if(dist1 == 0.0) //parallel
	{ return false; }

	vec3_t origin;
	VectorScale(plane.normal,plane.dist,origin);
	VectorSubtract(origin,p1,origin);

	vec_t loc = DotProduct(plane.normal,origin)/dist1; //p1 to plane
	if(loc < 0 || loc > 1) //if we're over 100% or 
	{ return false; } //under 0% of original length

	VectorMA(p1,loc,line,point);
	return true;
}

//==================================================
// PlaneFromPoints
//==================================================

inline void PlaneFromPoints(const vec_t* const p1, const vec_t* const p2, const vec_t* const p3, dplane_t* plane)
{
	vec3_t	temp, temp2;
	VectorSubtract(p3,p2,temp);
	VectorSubtract(p1,p2,temp2);
	CrossProduct(temp,temp2,plane->normal);
	VectorNormalize(plane->normal);
	plane->dist = DotProduct(plane->normal,p1);
}

//==================================================
// SnapToPlane
//==================================================

inline void SnapToPlane(const dplane_t* const plane, vec_t* const point, const vec_t offset)
{
	vec_t scale = (plane->dist + offset);
	scale -= DotProduct(plane->normal,point);
	VectorMA(point,scale,plane->normal,point);
}

// =====================================================================================
//  TestSegmentAgainstOpaqueList
//      Returns facenum of the opaque face if the segment intersects an item in the opaque list
// =====================================================================================
#ifdef HLRAD_HULLU
int TestSegmentAgainstOpaqueList(const vec_t* p1, const vec_t* p2, vec3_t &scaleout)
#else
int TestSegmentAgainstOpaqueList(const vec_t* p1, const vec_t* p2)
#endif
{
    unsigned        x;
    vec3_t          point;
    const dplane_t* plane;
    const Winding*  winding;
	int             returnVal = -1;
	int             facenum;
	//vec_t*			endpoint;
	bool blocked = false;

	//*endpoint = *p2;

#ifdef HLRAD_HULLU
    VectorFill(scaleout,1.0);
#endif

    for (x = 0; x < g_opaque_face_count; x++)
    {
        plane = &g_opaque_face_list[x].plane;
        winding = g_opaque_face_list[x].winding;
		facenum = g_opaque_face_list[x].facenum;

//		if(LineSegmentIntersectsFace(p1,endpoint,point,x))
		if(LineSegmentIntersectsFace(p1,p2,point,x))
		{
#ifdef HLRAD_HULLU
		    if(g_opaque_face_list[x].transparency)
		    {
			    VectorMultiply(scaleout, g_opaque_face_list[x].transparency_scale, scaleout);
				//don't change endpoint here -- only if face completely blocks light
		    }
            else
            {
				//can't break here because we might be blocked more than one time,
				//continue searching for earliest blocking face
				//*endpoint = *point;
				returnVal = facenum;
                blocked = true;
            }
#else
			*endpoint = *point;
			returnVal = facenum;
            blocked = true;
#endif
        }
	}
	if(blocked)
	{
		return returnVal; //return face that intersects the segment closest to p1
	}
#ifdef HLRAD_HULLU
	if(VectorCompare(scaleout,vec3_origin))
	{
		VectorClear(scaleout);
	}
#endif
    return -1; //not completely blocked
}