//-----------------------------------------------------------------------------------------------
// Raycast2D.cpp
//
#include "GypsyPrecompiledHeader.hpp"

//-----------------------------------------------------------------------------------------------
#include "Engine/Core/Raycast2D.hpp"


//-----------------------------------------------------------------------------------------------
TraceResult2D::TraceResult2D()
	: m_didImpact( false )
	, m_impactFraction( 0.f )
	, m_impactPosition( 0.f, 0.f )
	, m_impactSurfaceNormal( 0.f, 0.f )
	, m_impactTileCoords( 0, 0 )
{
}


//-----------------------------------------------------------------------------------------------
void Raycast2D( const Vector2& start, const Vector2& end, TraceResult2D& traceResult, Raycast2DIsTileSolidFunction* isTileSolidCallback )
{
	IntVector2 currentTileCoords( static_cast< int >( floor( start.x ) ), static_cast< int >( floor( start.y ) ) );

	if( isTileSolidCallback( currentTileCoords ) )
	{
		// Ray start point is inside solid
		traceResult.m_didImpact = true;
		traceResult.m_impactTileCoords = currentTileCoords;
		traceResult.m_impactFraction = 0.f;
		traceResult.m_impactPosition = start;
		traceResult.m_impactSurfaceNormal.SetXY( 0.f, 0.f );
		return;
	}

	const float VERY_LARGE_FLOAT = FLT_MAX;
	const Vector2 rayDisplacement = end - start;
	const float tDeltaX = rayDisplacement.x != 0.f ? abs(1.f / rayDisplacement.x) : VERY_LARGE_FLOAT;
	const float tDeltaY = rayDisplacement.y != 0.f ? abs(1.f / rayDisplacement.y) : VERY_LARGE_FLOAT;
	const IntVector2 tileStepDirection( rayDisplacement.x < 0.f ? -1 : 1, rayDisplacement.y < 0.f ? -1 : 1 );
	const int offsetToLeadingEdgeX = (tileStepDirection.x + 1) >> 1;
	const int offsetToLeadingEdgeY = (tileStepDirection.y + 1) >> 1;
	const float firstVerticalIntersectionX = static_cast< float >( currentTileCoords.x + offsetToLeadingEdgeX );
	const float firstHorizontalIntersectionY = static_cast< float >( currentTileCoords.y + offsetToLeadingEdgeY);
	float tOfNextXCrossing = abs(firstVerticalIntersectionX - start.x) * tDeltaX;
	float tOfNextYCrossing = abs(firstHorizontalIntersectionY - start.y) * tDeltaY;

	for( ;; )
	{
		if( tOfNextXCrossing < tOfNextYCrossing )
		{
			if( tOfNextXCrossing > 1.f )
				break;

			currentTileCoords.x += tileStepDirection.x;
			if( isTileSolidCallback( currentTileCoords ) )
			{
				// Impact on solid tile!
				traceResult.m_didImpact = true;
				traceResult.m_impactTileCoords = currentTileCoords;
				traceResult.m_impactFraction = tOfNextXCrossing;
				traceResult.m_impactPosition = start + (rayDisplacement * tOfNextXCrossing);
				traceResult.m_impactSurfaceNormal.SetXY( static_cast< float >( -tileStepDirection.x ), 0.f );
				return;
			}

			tOfNextXCrossing += tDeltaX;
		}
		else
		{
			if( tOfNextYCrossing > 1.f )
				break;

			currentTileCoords.y += tileStepDirection.y;
			if( isTileSolidCallback( currentTileCoords ) )
			{
				// Impact on solid tile!
				traceResult.m_didImpact = true;
				traceResult.m_impactTileCoords = currentTileCoords;
				traceResult.m_impactFraction = tOfNextYCrossing;
				traceResult.m_impactPosition = start + (rayDisplacement * tOfNextYCrossing);
				traceResult.m_impactSurfaceNormal.SetXY( 0.f, static_cast< float >( -tileStepDirection.y ) );
				return;
			}

			tOfNextYCrossing += tDeltaY;
		}
	}

	traceResult.m_didImpact = false;
	traceResult.m_impactTileCoords.SetXY( -1, -1 );
	traceResult.m_impactFraction = 1.f;
	traceResult.m_impactPosition = end;
	traceResult.m_impactSurfaceNormal.SetXY( 0.f, 0.f );
	return;
}


