/*

	Copyright (C) 2004 Michael Liebscher
	Copyright (C) 2000-2002 by DarkOne the Hacker

	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.

*/

/*
 *	wolf_raycast.c:  Wolfenstein3-D ray-casting.
 *	
 *	Author:	Michael Liebscher <johnnycanuck@users.sourceforge.net>
 *	Date:	2004 
 *
 *	Acknowledgement:
 *	This code was derived from NewWolf, and was originally
 *	written by DarkOne the Hacker.
 *
 */

#include "../wolfiphone.h"


W8 tile_visible[ 64 ][ 64 ]; // can player see this tile?


/*
-----------------------------------------------------------------------------
 Function: R_RayCast() -Ray cast viewport. 
 
 Parameters: viewport -[in] Position of camera.
			 lvl -[in] Pointer to valid LevelData_t structure.
 
 Returns: Nothing.
 
 Notes: Marks all visible tiles in tile_visible[] array.

-----------------------------------------------------------------------------
*/
PUBLIC void R_RayCast( placeonplane_t viewport, LevelData_t *lvl )
{
	int n, x, y, angle, vx, vy;
	r_trace_t trace;

	memset( tile_visible, 0, sizeof( tile_visible ) ); // clear tile visible flags

// viewport tile coordinates
	x = (int)viewport.origin[ 0 ];
	y = (int)viewport.origin[ 1 ];
	angle = (int)viewport.angle;

	vx = (int)POS2TILE( viewport.origin[ 0 ] );
	vy = (int)POS2TILE( viewport.origin[ 1 ] );

	trace.tile_vis = tile_visible;
	trace.flags = TRACE_SIGHT | TRACE_MARK_MAP;

//
// Ray casting
//

	// FIXME: control ray count and make angle init
	for( n = 0 ; n < 640 ; ++n ) 
	{
		trace.x = x;
		trace.y = y;
		trace.a = NormalizeAngle( angle + ColumnAngle[ n ] );

		R_Trace( &trace, lvl );
	}

//
// Rendering
//
	for( x = 0 ; x < 64; ++x )
	for( y = 0 ; y < 64; ++y )
	if( tile_visible[ x ][ y ] )
	{
		lvl->tileEverVisible[x][y] = 1;	// for automap
		if( lvl->tilemap[ x ][ y ] & DOOR_TILE )
		{
		/* door */
			if( lvl->Doors.DoorMap[ x ][ y ].action != dr_open )
			{
				_boolean backside = false;

				if( lvl->Doors.DoorMap[ x ][ y ].vertical )
				{
					if( x < vx ) 
						backside = true;
				}
				else
				{
					if( y < vy ) 
						backside = true;
				}

				R_Draw_Door( x, y, LOWERZCOORD, UPPERZCOORD, 
							 lvl->Doors.DoorMap[ x ][ y ].vertical,
							 backside, 
							 lvl->Doors.DoorMap[ x ][ y ].texture, 
							 Door_Opened( &lvl->Doors, x, y ) );
			}
		/* door sides */
			if( lvl->Doors.DoorMap[ x ][ y ].vertical )
			{
				if( y <= vy )
					R_Draw_Wall( (float)x, (float)(y-1), LOWERZCOORD, UPPERZCOORD, dir4_north, TEX_PLATE );

				if( y >= vy )
					R_Draw_Wall( (float)x, (float)(y+1), LOWERZCOORD, UPPERZCOORD, dir4_south, TEX_PLATE );

				if( x <= vx && lvl->tilemap[ x - 1 ][ y ] & WALL_TILE )
					R_Draw_Wall( (float)(x-1), (float)y, LOWERZCOORD, UPPERZCOORD, dir4_east, lvl->wall_tex_x[ x - 1 ][ y ] );
				
				if( x >= vx && lvl->tilemap[ x + 1 ][ y ] & WALL_TILE )
					R_Draw_Wall( (float)(x+1), (float)y, LOWERZCOORD, UPPERZCOORD, dir4_west, lvl->wall_tex_x[ x + 1 ][ y ] );
			}
			else
			{
				if( x <= vx )
					R_Draw_Wall((float)(x-1), (float)y, LOWERZCOORD, UPPERZCOORD, dir4_east, TEX_PLATE+1);

				if( x >= vx )
					R_Draw_Wall((float)(x+1), (float)y, LOWERZCOORD, UPPERZCOORD, dir4_west, TEX_PLATE+1);

				if( y <= vy && lvl->tilemap[ x ][ y - 1 ] & WALL_TILE )
					R_Draw_Wall( (float)x, (float)(y-1), LOWERZCOORD, UPPERZCOORD, dir4_north, lvl->wall_tex_y[x][y-1]);
				
				if( y >= vy && lvl->tilemap[ x ][ y + 1 ] & WALL_TILE )
					R_Draw_Wall( (float)x, (float)(y+1), LOWERZCOORD, UPPERZCOORD, dir4_south, lvl->wall_tex_y[x][y+1]);
			}
		}
		else
		{
			/* Push-Wall */
			if( (r_world->tilemap[ x ][ y ] & PUSHWALL_TILE) )
			{
				float dx, dy;

				dx = PWall.dx * PWall.PWpointsmoved / 128.0f;
				dy = PWall.dy * PWall.PWpointsmoved / 128.0f;

				if( PWall.x <= vx )
					R_Draw_Wall( (float)PWall.x + dx, (float)PWall.y + dy, LOWERZCOORD, UPPERZCOORD, dir4_east, PWall.tex_x );

				if( PWall.x >= vx )
					R_Draw_Wall( (float)PWall.x + dx, (float)PWall.y + dy, LOWERZCOORD, UPPERZCOORD, dir4_west, PWall.tex_x );

				if( PWall.y <= vy )
					R_Draw_Wall( (float)PWall.x + dx, (float)PWall.y + dy, LOWERZCOORD, UPPERZCOORD, dir4_north, PWall.tex_y );

				if( PWall.y >= vy )
					R_Draw_Wall( (float)PWall.x + dx, (float)PWall.y + dy, LOWERZCOORD, UPPERZCOORD, dir4_south, PWall.tex_y );

			}
			/* x-wall */
			if( x <= vx && r_world->tilemap[ x - 1 ][ y ] & WALL_TILE )
				R_Draw_Wall( (float)(x-1), (float)y, LOWERZCOORD, UPPERZCOORD, dir4_east, r_world->wall_tex_x[x-1][y]);
			
			if( x >= vx && r_world->tilemap[ x + 1 ][ y ] & WALL_TILE )
				R_Draw_Wall( (float)(x+1), (float)y, LOWERZCOORD, UPPERZCOORD, dir4_west, r_world->wall_tex_x[x+1][y]);
			
			/* y-wall */
			if( y <= vy && r_world->tilemap[ x ][ y - 1 ] & WALL_TILE )
				R_Draw_Wall( (float)x, (float)(y-1), LOWERZCOORD, UPPERZCOORD, dir4_north, r_world->wall_tex_y[x][y-1]);
			
			if( y >= vy && r_world->tilemap[ x ][ y + 1 ] & WALL_TILE )
				R_Draw_Wall( (float)x, (float)(y+1), LOWERZCOORD, UPPERZCOORD, dir4_south, r_world->wall_tex_y[x][y+1]);
		}


	}

	// back to full brightness in case the last draw was a dim wall
	pfglColor3f( 1, 1, 1 );
}


int x_tile_step[ 4 ] = { 1, -1, -1,  1 };
int y_tile_step[ 4 ] = { 1,  1, -1, -1 };


/*
-----------------------------------------------------------------------------
 Function: R_TraceCheck() -Trace ray check. 
 
 Parameters: 
			 lvl -[in] Pointer to valid LevelData_t structure.
			 x, y -[in] In tiles.
 
 Returns: true to stop tracing, false otherwise.
 
 Notes: Tells ray casting if we hit a wall or door and to stop tracing.

-----------------------------------------------------------------------------
*/
PRIVATE _boolean R_TraceCheck( LevelData_t *lvl, int x, int y, int frac, int dfrac, _boolean vert, _boolean flip, r_trace_t *trace )
{
	if( lvl->tilemap[ x ][ y ] & WALL_TILE )
	{
		if( vert )
		{
			trace->x = (x << TILESHIFT) + (flip ? TILEGLOBAL : 0);
			trace->y = (y << TILESHIFT) + frac;
			trace->flags |= TRACE_HIT_VERT;
		}
		else
		{
			trace->x = (x << TILESHIFT) + frac;
			trace->y = (y << TILESHIFT) + (flip ? TILEGLOBAL : 0);
			trace->flags &= ~TRACE_HIT_VERT;
		}

		return true; // wall, stop tracing
	}

	if( trace->tile_vis )
	{
		trace->tile_vis[ x ][ y ] = true; // this tile is visible
	}


	if( lvl->tilemap[ x ][ y ] & DOOR_TILE &&
		lvl->Doors.DoorMap[ x ][ y ].action != dr_open )
	{
		frac += dfrac >> 1;
		if( POS2TILE( frac ) )
			return false;

		if( vert )
		{
			if( lvl->Doors.DoorMap[ x ][ y ].action != dr_closed &&
				(frac >> 10) > DOOR_FULLOPEN - Door_Opened( &lvl->Doors, x, y ) )
			{
				return false; // opened enough
			}

			trace->x = TILE2POS( x );
			trace->y = (y << TILESHIFT) + frac;
			trace->flags |= TRACE_HIT_VERT;
		}
		else
		{
			if( lvl->Doors.DoorMap[ x ][ y ].action != dr_closed &&
				(frac >> 10) < Door_Opened( &lvl->Doors, x, y ) )
			{
				return false; // opened enough
			}

			trace->y = TILE2POS( y );
			trace->x = (x << TILESHIFT) + frac;
			trace->flags &= ~TRACE_HIT_VERT;
		}
		trace->flags |= TRACE_HIT_DOOR;

		return true; // closed door, stop tracing
	}

	return false; // no intersection, go on!
}

/*
-----------------------------------------------------------------------------
 Function: R_Trace() -Trace ray. 
 
 Parameters: 
			 trace -[in] Pointer to valid r_trace_t structure.
			 lvl -[in] Pointer to valid LevelData_t structure.

 
 Returns: Nothing.
 
 Notes:

-----------------------------------------------------------------------------
*/
PUBLIC void R_Trace( r_trace_t *trace, LevelData_t *lvl )
{
	int xtilestep, ytilestep;
	int xstep, ystep;
	int xtile, ytile;
	int xintercept, yintercept;
	int YmapPos, XmapPos;
	quadrant q;

// Setup for ray casting
	q = GetQuadrant( FINE2RAD( trace->a ) );

	xtilestep = x_tile_step[ q ];
	ytilestep = y_tile_step[ q ];

	xtile = POS2TILE( trace->x ) + xtilestep;
	ytile = POS2TILE( trace->y ) + ytilestep;

	xstep = ytilestep * XnextTable[ trace->a ];
	ystep = xtilestep * YnextTable[ trace->a ];

	xintercept = (int)( ( ((ytilestep == -1 ? ytile+1 : ytile) << TILESHIFT) - trace->y ) / TanTable[ trace->a ]) + trace->x;
	yintercept = (int)( ( ((xtilestep == -1 ? xtile+1 : xtile) << TILESHIFT) - trace->x ) * TanTable[ trace->a ]) + trace->y;

	YmapPos = yintercept >> TILESHIFT; // toXray
	XmapPos = xintercept >> TILESHIFT; // toYray

	if( trace->tile_vis )
	{
		// this tile is visible
		trace->tile_vis[ POS2TILE( trace->x ) ][ POS2TILE( trace->y ) ] = true; 
	}
	
//
// Start of ray-casting
//
	while( 1 )
	{
//
// Vertical loop // an anologue for X-Ray
//
		while( ! (ytilestep == -1 && YmapPos <= ytile) && ! (ytilestep == 1 && YmapPos >= ytile) )
		{
			if( xtile < 0 || xtile >= 64 || YmapPos < 0 || YmapPos >= 64 )
			{
				return;
			}

			if( R_TraceCheck( lvl, xtile, YmapPos, yintercept % TILEGLOBAL, ystep, true, (_boolean)(xtilestep == -1), trace ) )
			{
				return;
			}

		// prepare for next step
			xtile += xtilestep;
			yintercept += ystep;
			YmapPos = yintercept >> TILESHIFT;
		}

//
// Horizontal loop // an anologue for Y-Ray
//
		while( ! (xtilestep == -1 && XmapPos <= xtile) && ! (xtilestep == 1 && XmapPos >= xtile) )
		{
			if( ytile < 0 || ytile >= 64 || XmapPos < 0 || XmapPos >= 64 )
			{
				return;
			}

			if( R_TraceCheck( lvl, XmapPos, ytile, xintercept % TILEGLOBAL, xstep, false, (_boolean)(ytilestep == -1), trace ) )
			{
				return;
			}

		// prepare for next step
			ytile += ytilestep;
			xintercept += xstep;
			XmapPos = xintercept >> TILESHIFT;
		}

	} // end of while( 1 )

}
