// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2018 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  r_splats.c
/// \brief floor and wall splats

#include "r_draw.h"
#include "r_main.h"
#include "r_plane.h"
#include "r_splats.h"
#include "w_wad.h"
#include "z_zone.h"
#include "d_netcmd.h"

#ifdef WALLSPLATS
static wallsplat_t wallsplats[MAXLEVELSPLATS]; // WALL splats
static INT32 freewallsplat;
#endif

#ifdef USEASM
/// \brief for floorsplats \note accessed by asm code
struct rastery_s *prastertab;
#endif

#ifdef FLOORSPLATS
static floorsplat_t floorsplats[1]; // FLOOR splats
static INT32 freefloorsplat;

struct rastery_s
{
	fixed_t minx, maxx; // for each raster line starting at line 0
	fixed_t tx1, ty1;
	fixed_t tx2, ty2; // start/end points in texture at this line
};
static struct rastery_s rastertab[MAXVIDHEIGHT];

static void prepare_rastertab(void);
#endif

// --------------------------------------------------------------------------
// setup splat cache
// --------------------------------------------------------------------------
void R_ClearLevelSplats(void)
{
#ifdef WALLSPLATS
	freewallsplat = 0;
	memset(wallsplats, 0, sizeof (wallsplats));
#endif
#ifdef FLOORSPLATS
	freefloorsplat = 0;
	memset(floorsplats, 0, sizeof (floorsplats));

	// setup to draw floorsplats
	prastertab = rastertab;
	prepare_rastertab();
#endif
}

// ==========================================================================
//                                                                WALL SPLATS
// ==========================================================================
#ifdef WALLSPLATS
// --------------------------------------------------------------------------
// Return a pointer to a splat free for use, or NULL if no more splats are
// available
// --------------------------------------------------------------------------
static wallsplat_t *R_AllocWallSplat(void)
{
	wallsplat_t *splat;
	wallsplat_t *p_splat;
	line_t *li;

	// clear the splat from the line if it was in use
	splat = &wallsplats[freewallsplat];
	li = splat->line;
	if (li)
	{
		// remove splat from line splats list
		if (li->splats == splat)
			li->splats = splat->next; // remove from head
		else
		{
			I_Assert(li->splats != NULL);
			for (p_splat = li->splats; p_splat->next; p_splat = p_splat->next)
				if (p_splat->next == splat)
				{
					p_splat->next = splat->next;
					break;
				}
		}
	}

	memset(splat, 0, sizeof (wallsplat_t));

	// for next allocation
	freewallsplat++;
	if (freewallsplat >= 20)
		freewallsplat = 0;

	return splat;
}

// Add a new splat to the linedef:
// top: top z coord
// wallfrac: frac along the linedef vector (0 to FRACUNIT)
// splatpatchname: name of patch to draw
void R_AddWallSplat(line_t *wallline, INT16 sectorside, const char *patchname, fixed_t top,
	fixed_t wallfrac, INT32 flags)
{
	fixed_t fracsplat, linelength;
	wallsplat_t *splat = NULL;
	wallsplat_t *p_splat;
	patch_t *patch;
	sector_t *backsector = NULL;

	if (W_CheckNumForName(patchname) != LUMPERROR)
		splat = R_AllocWallSplat();
	if (!splat)
		return;

	// set the splat
	splat->patch = W_GetNumForName(patchname);
	sectorside ^= 1;
	if (wallline->sidenum[sectorside] != 0xffff)
	{
		backsector = sides[wallline->sidenum[sectorside]].sector;

		if (top < backsector->floorheight)
		{
			splat->yoffset = &backsector->floorheight;
			top -= backsector->floorheight;
		}
		else if (top > backsector->ceilingheight)
		{
			splat->yoffset = &backsector->ceilingheight;
			top -= backsector->ceilingheight;
		}
	}

	splat->top = top;
	splat->flags = flags;

	// bad.. but will be needed for drawing anyway..
	patch = W_CachePatchNum(splat->patch, PU_CACHE);

	// offset needed by draw code for texture mapping
	linelength = P_SegLength((seg_t *)wallline);
	splat->offset = FixedMul(wallfrac, linelength) - (SHORT(patch->width)<<(FRACBITS-1));
	fracsplat = FixedDiv(((SHORT(patch->width)<<FRACBITS)>>1), linelength);

	wallfrac -= fracsplat;
	if (wallfrac > linelength)
		return;
	splat->v1.x = wallline->v1->x + FixedMul(wallline->dx, wallfrac);
	splat->v1.y = wallline->v1->y + FixedMul(wallline->dy, wallfrac);
	wallfrac += fracsplat + fracsplat;
	if (wallfrac < 0)
		return;
	splat->v2.x = wallline->v1->x + FixedMul(wallline->dx, wallfrac);
	splat->v2.y = wallline->v1->y + FixedMul(wallline->dy, wallfrac);

	if (wallline->frontsector && wallline->frontsector == backsector)
		return;

	// insert splat in the linedef splat list
	// BP: why not insert in head is much more simple?
	// BP: because for remove it is more simple!
	splat->line = wallline;
	splat->next = NULL;
	if (wallline->splats)
	{
		p_splat = wallline->splats;
		while (p_splat->next)
			p_splat = p_splat->next;
		p_splat->next = splat;
	}
	else
		wallline->splats = splat;
}
#endif // WALLSPLATS

// ==========================================================================
//                                                               FLOOR SPLATS
// ==========================================================================
#ifdef FLOORSPLATS

// --------------------------------------------------------------------------
// Return a pointer to a splat free for use, or NULL if no more splats are
// available
// --------------------------------------------------------------------------
static floorsplat_t *R_AllocFloorSplat(void)
{
	floorsplat_t *splat;
	floorsplat_t *p_splat;
	subsector_t *sub;

	// find splat to use
	freefloorsplat++;
	if (freefloorsplat >= 1)
		freefloorsplat = 0;

	// clear the splat from the line if it was in use
	splat = &floorsplats[freefloorsplat];
	sub = splat->subsector;
	if (sub)
	{
		// remove splat from subsector splats list
		if (sub->splats == splat)
			sub->splats = splat->next; // remove from head
		else
		{
			p_splat = sub->splats;
			while (p_splat->next)
			{
				if (p_splat->next == splat)
					p_splat->next = splat->next;
			}
		}
	}

	memset(splat, 0, sizeof (floorsplat_t));
	return splat;
}

// --------------------------------------------------------------------------
// Add a floor splat to the subsector
// --------------------------------------------------------------------------
void R_AddFloorSplat(subsector_t *subsec, mobj_t *mobj, const char *picname, fixed_t x, fixed_t y, fixed_t z,
	INT32 flags)
{
	floorsplat_t *splat = NULL;
	floorsplat_t *p_splat;
	INT32 size;

	if (W_CheckNumForName(picname) != LUMPERROR)
		splat = R_AllocFloorSplat();
	if (!splat)
		return;

	// set the splat
	splat->pic = W_GetNumForName(picname);
	splat->flags = flags;
	splat->mobj = mobj;

	splat->z = z;

	size = W_LumpLength(splat->pic);

	switch (size)
	{
		case 4194304: // 2048x2048 lump
			splat->size = 1024;
			break;
		case 1048576: // 1024x1024 lump
			splat->size = 512;
			break;
		case 262144:// 512x512 lump
			splat->size = 256;
			break;
		case 65536: // 256x256 lump
			splat->size = 128;
			break;
		case 16384: // 128x128 lump
			splat->size = 64;
			break;
		case 1024: // 32x32 lump
			splat->size = 16;
			break;
		default: // 64x64 lump
			splat->size = 32;
			break;
	}

	// 3--2
	// |  |
	// 0--1
	//
	splat->verts[0].x = splat->verts[3].x = x - (splat->size<<FRACBITS);
	splat->verts[2].x = splat->verts[1].x = x + ((splat->size-1)<<FRACBITS);
	splat->verts[3].y = splat->verts[2].y = y + ((splat->size-1)<<FRACBITS);
	splat->verts[0].y = splat->verts[1].y = y - (splat->size<<FRACBITS);

	// insert splat in the subsector splat list
	splat->subsector = subsec;
	splat->next = NULL;
	if (subsec->splats)
	{
		p_splat = subsec->splats;
		while (p_splat->next)
			p_splat = p_splat->next;
		p_splat->next = splat;
	}
	else
		subsec->splats = splat;
}

// --------------------------------------------------------------------------
// Before each frame being rendered, clear the visible floorsplats list
// --------------------------------------------------------------------------
static floorsplat_t *visfloorsplats;

void R_ClearVisibleFloorSplats(void)
{
	visfloorsplats = NULL;
}

// --------------------------------------------------------------------------
// Add a floorsplat to the visible floorsplats list, for the current frame
// --------------------------------------------------------------------------
void R_AddVisibleFloorSplats(subsector_t *subsec)
{
	floorsplat_t *pSplat;
	I_Assert(subsec->splats != NULL);

	pSplat = subsec->splats;
	// the splat is not visible from below
	// FIXME: depending on some flag in pSplat->flags, some splats may be visible from 2 sides
	// (above/below)
	if (pSplat->z < viewz)
	{
		pSplat->nextvis = visfloorsplats;
		visfloorsplats = pSplat;
	}

	while (pSplat->next)
	{
		pSplat = pSplat->next;
		if (pSplat->z < viewz)
		{
			pSplat->nextvis = visfloorsplats;
			visfloorsplats = pSplat;
		}
	}
}

#ifdef USEASM
// tv1, tv2 = x/y qui varie dans la texture, tc = x/y qui est constant.
void ASMCALL rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2,
	INT32 tc, INT32 dir);
#endif

// current test with floor tile
//#define FLOORSPLATSOLIDCOLOR

// --------------------------------------------------------------------------
// Rasterize the four edges of a floor splat polygon,
// fill the polygon with linear interpolation, call span drawer for each
// scan line
// --------------------------------------------------------------------------
static void R_RenderFloorSplat(floorsplat_t *pSplat, vertex_t *verts, UINT8 *pTex)
{
	// rasterizing
	INT32 miny = vid.height + 1, maxy = 0, y, x1, ry1, x2, y2;
	fixed_t offsetx, offsety;

#ifdef FLOORSPLATSOLIDCOLOR
	UINT8 *pDest;
	INT32 tdx, tdy, ty, tx, x;
#else
	lighttable_t **planezlight;
	fixed_t planeheight;
	angle_t angle, planecos, planesin;
	fixed_t distance, span;
	size_t indexr;
	INT32 light;
#endif
	(void)pTex;

	offsetx = pSplat->verts[0].x & ((pSplat->size << FRACBITS)-1);
	offsety = pSplat->verts[0].y & ((pSplat->size << FRACBITS)-1);

	// do segment a -> top of texture
	x1 = verts[3].x;
	ry1 = verts[3].y;
	x2 = verts[2].x;
	y2 = verts[2].y;
	if (ry1 < 0)
		ry1 = 0;
	if (ry1 >= vid.height)
		ry1 = vid.height - 1;
	if (y2 < 0)
		y2 = 0;
	if (y2 >= vid.height)
		y2 = vid.height - 1;
	rasterize_segment_tex(x1, ry1, x2, y2, 0, pSplat->size - 1, 0, 0);
	if (ry1 < miny)
		miny = ry1;
	if (ry1 > maxy)
		maxy = ry1;

	// do segment b -> right side of texture
	x1 = x2;
	ry1 = y2;
	x2 = verts[1].x;
	y2 = verts[1].y;
	if (ry1 < 0)
		ry1 = 0;
	if (ry1 >= vid.height)
		ry1 = vid.height - 1;
	if (y2 < 0)
		y2 = 0;
	if (y2 >= vid.height)
		y2 = vid.height - 1;
	rasterize_segment_tex(x1, ry1, x2, y2, 0, pSplat->size - 1, pSplat->size - 1, 1);
	if (ry1 < miny)
		miny = ry1;
	if (ry1 > maxy)
		maxy = ry1;

	// do segment c -> bottom of texture
	x1 = x2;
	ry1 = y2;
	x2 = verts[0].x;
	y2 = verts[0].y;
	if (ry1 < 0)
		ry1 = 0;
	if (ry1 >= vid.height)
		ry1 = vid.height - 1;
	if (y2 < 0)
		y2 = 0;
	if (y2 >= vid.height)
		y2 = vid.height - 1;
	rasterize_segment_tex(x1, ry1, x2, y2, pSplat->size - 1, 0, pSplat->size - 1, 0);
	if (ry1 < miny)
		miny = ry1;
	if (ry1 > maxy)
		maxy = ry1;

	// do segment d -> left side of texture
	x1 = x2;
	ry1 = y2;
	x2 = verts[3].x;
	y2 = verts[3].y;
	if (ry1 < 0)
		ry1 = 0;
	if (ry1 >= vid.height)
		ry1 = vid.height - 1;
	if (y2 < 0)
		y2 = 0;
	if (y2 >= vid.height)
		y2 = vid.height - 1;
	rasterize_segment_tex(x1, ry1, x2, y2, pSplat->size - 1, 0, 0, 1);
	if (ry1 < miny)
		miny = ry1;
	if (ry1 > maxy)
		maxy = ry1;

#ifndef FLOORSPLATSOLIDCOLOR
	// prepare values for all the splat
	ds_source = W_CacheLumpNum(pSplat->pic, PU_CACHE);
	planeheight = abs(pSplat->z - viewz);
	light = (pSplat->subsector->sector->lightlevel >> LIGHTSEGSHIFT);
	if (light >= LIGHTLEVELS)
		light = LIGHTLEVELS - 1;
	if (light < 0)
		light = 0;
	planezlight = zlight[light];

	for (y = miny; y <= maxy; y++)
	{
		x1 = rastertab[y].minx>>FRACBITS;
		x2 = rastertab[y].maxx>>FRACBITS;

		if (x1 < 0)
			x1 = 0;
		if (x2 >= vid.width)
			x2 = vid.width - 1;

		angle = (currentplane->viewangle + currentplane->plangle)>>ANGLETOFINESHIFT;
		planecos = FINECOSINE(angle);
		planesin = FINESINE(angle);

		if (planeheight != cachedheight[y])
		{
			cachedheight[y] = planeheight;
			distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
			ds_xstep = cachedxstep[y] = FixedMul(distance,basexscale);
			ds_ystep = cachedystep[y] = FixedMul(distance,baseyscale);

			if ((span = abs(centery-y)))
			{
				ds_xstep = cachedxstep[y] = FixedMul(planesin, planeheight) / span;
				ds_ystep = cachedystep[y] = FixedMul(planecos, planeheight) / span;
			}
		}
		else
		{
			distance = cacheddistance[y];
			ds_xstep = cachedxstep[y];
			ds_ystep = cachedystep[y];
		}

		ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
		ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
		ds_xfrac -= offsetx;
		ds_yfrac += offsety;

		indexr = distance >> LIGHTZSHIFT;
		if (indexr >= MAXLIGHTZ)
			indexr = MAXLIGHTZ - 1;
		ds_colormap = planezlight[indexr];

		ds_y = y;
		if (x2 >= x1) // sanity check
		{
			ds_x1 = x1;
			ds_x2 = x2;
			ds_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
			splatfunc();
		}

		// reset for next calls to edge rasterizer
		rastertab[y].minx = INT32_MAX;
		rastertab[y].maxx = INT32_MIN;
	}

#else
	for (y = miny; y <= maxy; y++)
	{
		x1 = rastertab[y].minx>>FRACBITS;
		x2 = rastertab[y].maxx>>FRACBITS;
		if (x1 < 0)
			x1 = 0;
		if (x2 >= vid.width)
			x2 = vid.width - 1;

//		pDest = ylookup[y] + columnofs[x1];
		pDest = &topleft[y*vid.width + x1];

		x = x2 - x1 + 1;

		// starting point of the texture
		tx = rastertab[y].tx1;
		ty = rastertab[y].ty1;

		// HORRIBLE BUG!!!
		if (x > 0)
		{
			tdx = (rastertab[y].tx2 - tx) / x;
			tdy = (rastertab[y].ty2 - ty) / x;

			while (x-- > 0)
			{
				*(pDest++) = (UINT8)(y&1);
				tx += tdx;
				ty += tdy;
			}
		}

		// reinitialise the minimum and maximum for the next approach
		rastertab[y].minx = INT32_MAX;
		rastertab[y].maxx = INT32_MIN;
	}
#endif
}

// --------------------------------------------------------------------------
// R_DrawVisibleFloorSplats
// draw the flat floor/ceiling splats
// --------------------------------------------------------------------------
void R_DrawVisibleFloorSplats(void)
{
	floorsplat_t *pSplat;
	INT32 iCount = 0, i;
	fixed_t tr_x, tr_y, rot_x, rot_y, rot_z, xscale, yscale;
	vertex_t *v3d;
	vertex_t v2d[4];

	pSplat = visfloorsplats;
	while (pSplat)
	{
		iCount++;

		// Draw a floor splat
		// 3--2
		// |  |
		// 0--1

		rot_z = pSplat->z - viewz;
		for (i = 0; i < 4; i++)
		{
			v3d = &pSplat->verts[i];

			// transform the origin point
			tr_x = v3d->x - viewx;
			tr_y = v3d->y - viewy;

			// rotation around vertical y axis
			rot_x = FixedMul(tr_x, viewsin) - FixedMul(tr_y, viewcos);
			rot_y = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);

			if (rot_y < 4*FRACUNIT)
				goto skipit;

			// note: y from view above of map, is distance far away
			xscale = FixedDiv(projection, rot_y);
			yscale = -FixedDiv(projectiony, rot_y);

			// projection
			v2d[i].x = (centerxfrac + FixedMul (rot_x, xscale))>>FRACBITS;
			v2d[i].y = (centeryfrac + FixedMul (rot_z, yscale))>>FRACBITS;
		}

		R_RenderFloorSplat(pSplat, v2d, NULL);
skipit:
		pSplat = pSplat->nextvis;
	}
}

static void prepare_rastertab(void)
{
	INT32 iLine;
	for (iLine = 0; iLine < vid.height; iLine++)
	{
		rastertab[iLine].minx = INT32_MAX;
		rastertab[iLine].maxx = INT32_MIN;
	}
}

#endif // FLOORSPLATS
