// Emacs style mode select	 -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// $Log:$
//
// DESCRIPTION:
//		Refresh of things, i.e. objects represented by sprites.
//
// This file contains some code from the Build Engine.
//
// "Build Engine & Tools" Copyright (c) 1993-1997 Ken Silverman
// Ken Silverman's official web site: "http://www.advsys.net/ken"
// See the included license file "BUILDLIC.TXT" for license info.
//
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>

#include "p_lnspec.h"
#include "templates.h"
#include "doomdef.h"
#include "m_swap.h"
#include "i_system.h"
#include "w_wad.h"
#include "r_local.h"
#include "c_console.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "doomstat.h"
#include "v_video.h"
#include "sc_man.h"
#include "s_sound.h"
#include "sbar.h"
#include "gi.h"
#include "r_sky.h"
#include "cmdlib.h"
#include "g_level.h"
#include "d_net.h"
#include "colormatcher.h"
#include "d_netinf.h"
#include "p_effect.h"
#include "r_bsp.h"
#include "r_plane.h"
#include "r_segs.h"
#include "r_3dfloors.h"
#include "r_draw_rgba.h"
#include "v_palette.h"
#include "r_data/r_translate.h"
#include "r_data/colormaps.h"
#include "r_data/voxels.h"
#include "p_local.h"
#include "p_maputl.h"

EXTERN_CVAR(Bool, st_scale)
EXTERN_CVAR(Bool, r_shadercolormaps)
EXTERN_CVAR(Int, r_drawfuzz)
EXTERN_CVAR(Bool, r_deathcamera);
EXTERN_CVAR(Bool, r_drawplayersprites)
EXTERN_CVAR(Bool, r_drawvoxels)

CVAR(Bool, r_fullbrightignoresectorcolor, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
CVAR(Bool, r_splitsprites, true, CVAR_ARCHIVE)

namespace swrenderer
{
	using namespace drawerargs;

// [RH] A c-buffer. Used for keeping track of offscreen voxel spans.

struct FCoverageBuffer
{
	struct Span
	{
		Span *NextSpan;
		short Start, Stop;
	};

	FCoverageBuffer(int size);
	~FCoverageBuffer();

	void Clear();
	void InsertSpan(int listnum, int start, int stop);
	Span *AllocSpan();

	FMemArena SpanArena;
	Span **Spans;	// [0..NumLists-1] span lists
	Span *FreeSpans;
	unsigned int NumLists;
};

extern double globaluclip, globaldclip;
extern float MaskedScaleY;

#define MINZ			double((2048*4) / double(1 << 20))
#define BASEXCENTER		(160)
#define BASEYCENTER 	(100)

//
// Sprite rotation 0 is facing the viewer,
//	rotation 1 is one angle turn CLOCKWISE around the axis.
// This is not the same as the angle,
//	which increases counter clockwise (protractor).
//
double 			pspritexscale;
double	 		pspritexiscale;
double			pspriteyscale;
fixed_t			sky1scale;			// [RH] Sky 1 scale factor
fixed_t			sky2scale;			// [RH] Sky 2 scale factor

// Used to store a psprite's drawing information if it needs to be drawn later.
struct vispsp_t
{
	vissprite_t			*vis;
	FDynamicColormap	*basecolormap;
	int					x1;
};
TArray<vispsp_t>	vispsprites;
unsigned int		vispspindex;

static int		spriteshade;

FTexture		*WallSpriteTile;

// constant arrays
//	used for psprite clipping and initializing clipping
short			zeroarray[MAXWIDTH];
short			screenheightarray[MAXWIDTH];


//
// INITIALIZATION FUNCTIONS
//

int OffscreenBufferWidth, OffscreenBufferHeight;
BYTE *OffscreenColorBuffer;
FCoverageBuffer *OffscreenCoverageBuffer;

//

// GAME FUNCTIONS
//
int				MaxVisSprites;
vissprite_t 	**vissprites;
vissprite_t		**firstvissprite;
vissprite_t		**vissprite_p;
vissprite_t		**lastvissprite;
int 			newvissprite;
bool			DrewAVoxel;

static vissprite_t **spritesorter;
static int spritesortersize = 0;
static int vsprcount;

static void R_ProjectWallSprite(AActor *thing, const DVector3 &pos, FTextureID picnum, const DVector2 &scale, INTBOOL flip);



void R_DeinitSprites()
{
	// Free vissprites
	for (int i = 0; i < MaxVisSprites; ++i)
	{
		delete vissprites[i];
	}
	free (vissprites);
	vissprites = NULL;
	vissprite_p = lastvissprite = NULL;
	MaxVisSprites = 0;

	// Free vissprites sorter
	if (spritesorter != NULL)
	{
		delete[] spritesorter;
		spritesortersize = 0;
		spritesorter = NULL;
	}

	// Free offscreen buffer
	if (OffscreenColorBuffer != NULL)
	{
		delete[] OffscreenColorBuffer;
		OffscreenColorBuffer = NULL;
	}
	if (OffscreenCoverageBuffer != NULL)
	{
		delete OffscreenCoverageBuffer;
		OffscreenCoverageBuffer = NULL;
	}
	OffscreenBufferHeight = OffscreenBufferWidth = 0;
}

//
// R_ClearSprites
// Called at frame start.
//
void R_ClearSprites (void)
{
	vissprite_p = firstvissprite;
	DrewAVoxel = false;
}


//
// R_NewVisSprite
//
vissprite_t *R_NewVisSprite (void)
{
	if (vissprite_p == lastvissprite)
	{
		ptrdiff_t firstvisspritenum = firstvissprite - vissprites;
		ptrdiff_t prevvisspritenum = vissprite_p - vissprites;

		MaxVisSprites = MaxVisSprites ? MaxVisSprites * 2 : 128;
		vissprites = (vissprite_t **)M_Realloc (vissprites, MaxVisSprites * sizeof(vissprite_t));
		lastvissprite = &vissprites[MaxVisSprites];
		firstvissprite = &vissprites[firstvisspritenum];
		vissprite_p = &vissprites[prevvisspritenum];
		DPrintf (DMSG_NOTIFY, "MaxVisSprites increased to %d\n", MaxVisSprites);

		// Allocate sprites from the new pile
		for (vissprite_t **p = vissprite_p; p < lastvissprite; ++p)
		{
			*p = new vissprite_t;
		}
	}

	vissprite_p++;
	return *(vissprite_p-1);
}

//
// R_DrawMaskedColumn
// Used for sprites and masked mid textures.
// Masked means: partly transparent, i.e. stored
//	in posts/runs of opaque pixels.
//
short*			mfloorclip;
short*			mceilingclip;

double	 		spryscale;
double	 		sprtopscreen;

bool			sprflipvert;

void R_DrawMaskedColumnBgra(FTexture *tex, fixed_t col, bool useRt, bool unmasked)
{
	fixed_t saved_iscale = dc_iscale; // Save this because we need to modify it for mipmaps

	// Normalize to 0-1 range:
	double uv_stepd = FIXED2DBL(dc_iscale);
	double v_step = uv_stepd / tex->GetHeight();

	// Convert to uint32:
	dc_iscale = (uint32_t)(v_step * (1 << 30));

	// Texture mipmap and filter selection:
	fixed_t xoffset = col;

	double xmagnitude = 1.0; // To do: pass this into R_DrawMaskedColumn
	double ymagnitude = fabs(uv_stepd);
	double magnitude = MAX(ymagnitude, xmagnitude);
	double min_lod = -1000.0;
	double lod = MAX(log2(magnitude) + r_lod_bias, min_lod);
	bool magnifying = lod < 0.0f;

	int mipmap_offset = 0;
	int mip_width = tex->GetWidth();
	int mip_height = tex->GetHeight();
	uint32_t xpos = (uint32_t)((((uint64_t)xoffset) << FRACBITS) / mip_width);
	if (r_mipmap && tex->Mipmapped() && mip_width > 1 && mip_height > 1)
	{
		int level = (int)lod;
		while (level > 0 && mip_width > 1 && mip_height > 1)
		{
			mipmap_offset += mip_width * mip_height;
			level--;
			mip_width = MAX(mip_width >> 1, 1);
			mip_height = MAX(mip_height >> 1, 1);
		}
	}
	xoffset = (xpos >> FRACBITS) * mip_width;

	const uint32_t *pixels = tex->GetPixelsBgra() + mipmap_offset;

	bool filter_nearest = (magnifying && !r_magfilter) || (!magnifying && !r_minfilter);
	if (filter_nearest)
	{
		xoffset = MAX(MIN(xoffset, (mip_width << FRACBITS) - 1), 0);

		int tx = xoffset >> FRACBITS;
		dc_source = (BYTE*)(pixels + tx * mip_height);
		dc_source2 = nullptr;
		dc_textureheight = mip_height;
		dc_texturefracx = 0;
	}
	else
	{
		xoffset = MAX(MIN(xoffset - (FRACUNIT / 2), (mip_width << FRACBITS) - 1), 0);

		int tx0 = xoffset >> FRACBITS;
		int tx1 = MIN(tx0 + 1, mip_width - 1);
		dc_source = (BYTE*)(pixels + tx0 * mip_height);
		dc_source2 = (BYTE*)(pixels + tx1 * mip_height);
		dc_textureheight = mip_height;
		dc_texturefracx = (xoffset >> (FRACBITS - 4)) & 15;
	}

	// Grab the posts we need to draw
	const FTexture::Span *span;
	tex->GetColumnBgra(col >> FRACBITS, &span);
	FTexture::Span unmaskedSpan[2];
	if (unmasked)
	{
		span = unmaskedSpan;
		unmaskedSpan[0].TopOffset = 0;
		unmaskedSpan[0].Length = tex->GetHeight();
		unmaskedSpan[1].TopOffset = 0;
		unmaskedSpan[1].Length = 0;
	}

	// Draw each span post
	while (span->Length != 0)
	{
		const int length = span->Length;
		const int top = span->TopOffset;

		// calculate unclipped screen coordinates for post
		dc_yl = (int)(sprtopscreen + spryscale * top + 0.5);
		dc_yh = (int)(sprtopscreen + spryscale * (top + length) + 0.5) - 1;

		if (sprflipvert)
		{
			swapvalues(dc_yl, dc_yh);
		}

		if (dc_yh >= mfloorclip[dc_x])
		{
			dc_yh = mfloorclip[dc_x] - 1;
		}
		if (dc_yl < mceilingclip[dc_x])
		{
			dc_yl = mceilingclip[dc_x];
		}

		if (dc_yl <= dc_yh)
		{
			dc_dest = (ylookup[dc_yl] + dc_x) * 4 + dc_destorg;
			dc_count = dc_yh - dc_yl + 1;

			double v = ((dc_yl + 0.5 - sprtopscreen) / spryscale) / tex->GetHeight();
			dc_texturefrac = (uint32_t)(v * (1 << 30));

			if (useRt)
				hcolfunc_pre();
			else
				colfunc();
		}
		span++;
	}

	dc_iscale = saved_iscale;

	if (sprflipvert && useRt)
		rt_flip_posts();
}

void R_DrawMaskedColumn (FTexture *tex, fixed_t col, bool useRt, bool unmasked)
{
	// Handle the linear filtered version in a different function to reduce chances of merge conflicts from zdoom.
	if (r_swtruecolor && !drawer_needs_pal_input && !useRt) // To do: add support to R_DrawColumnHoriz_rgba
	{
		R_DrawMaskedColumnBgra(tex, col, useRt, unmasked);
		return;
	}

	const FTexture::Span *span;
	const BYTE *column;
	if (r_swtruecolor && !drawer_needs_pal_input)
		column = (const BYTE *)tex->GetColumnBgra(col >> FRACBITS, &span);
	else
		column = tex->GetColumn(col >> FRACBITS, &span);

	FTexture::Span unmaskedSpan[2];
	if (unmasked)
	{
		span = unmaskedSpan;
		unmaskedSpan[0].TopOffset = 0;
		unmaskedSpan[0].Length = tex->GetHeight();
		unmaskedSpan[1].TopOffset = 0;
		unmaskedSpan[1].Length = 0;
	}

	int pixelsize = r_swtruecolor ? 4 : 1;

	while (span->Length != 0)
	{
		const int length = span->Length;
		const int top = span->TopOffset;

		// calculate unclipped screen coordinates for post
		dc_yl = (int)(sprtopscreen + spryscale * top + 0.5);
		dc_yh = (int)(sprtopscreen + spryscale * (top + length) + 0.5) - 1;

		if (sprflipvert)
		{
			swapvalues (dc_yl, dc_yh);
		}

		if (dc_yh >= mfloorclip[dc_x])
		{
			dc_yh = mfloorclip[dc_x] - 1;
		}
		if (dc_yl < mceilingclip[dc_x])
		{
			dc_yl = mceilingclip[dc_x];
		}

		if (dc_yl <= dc_yh)
		{
			dc_texturefrac = FLOAT2FIXED((dc_yl + 0.5 - sprtopscreen) / spryscale);
			dc_source = column;
			dc_source2 = nullptr;
			dc_dest = (ylookup[dc_yl] + dc_x) * pixelsize + dc_destorg;
			dc_count = dc_yh - dc_yl + 1;

			fixed_t maxfrac = ((top + length) << FRACBITS) - 1;
			dc_texturefrac = MAX(dc_texturefrac, 0);
			dc_texturefrac = MIN(dc_texturefrac, maxfrac);
			if (dc_iscale > 0)
				dc_count = MIN(dc_count, (maxfrac - dc_texturefrac + dc_iscale - 1) / dc_iscale);
			else if (dc_iscale < 0)
				dc_count = MIN(dc_count, (dc_texturefrac - dc_iscale) / (-dc_iscale));

			if (useRt)
				hcolfunc_pre();
			else
				colfunc ();
		}
		span++;
	}

	if (sprflipvert && useRt)
		rt_flip_posts();
}

// [ZZ]
// R_ClipSpriteColumnWithPortals
//

static TArray<drawseg_t *> portaldrawsegs;

static inline void R_CollectPortals()
{
	// This function collects all drawsegs that may be of interest to R_ClipSpriteColumnWithPortals 
	// Having that function over the entire list of drawsegs can break down performance quite drastically.
	// This is doing the costly stuff only once so that R_ClipSpriteColumnWithPortals can 
	// a) exit early if no relevant info is found and
	// b) skip most of the collected drawsegs which have no portal attached.
	portaldrawsegs.Clear();
	for (drawseg_t* seg = ds_p; seg-- > firstdrawseg; ) // copied code from killough below
	{
		// I don't know what makes this happen (some old top-down portal code or possibly skybox code? something adds null lines...)
		// crashes at the first frame of the first map of Action2.wad
		if (!seg->curline) continue;

		line_t* line = seg->curline->linedef;
		// ignore minisegs from GL nodes.
		if (!line) continue;

		// check if this line will clip sprites to itself
		if (!line->isVisualPortal() && line->special != Line_Mirror)
			continue;

		// don't clip sprites with portal's back side (it's transparent)
		if (seg->curline->sidedef != line->sidedef[0])
			continue;

		portaldrawsegs.Push(seg);
	}
}

static inline bool R_ClipSpriteColumnWithPortals(vissprite_t* spr)
{
	// [ZZ] 10.01.2016: don't clip sprites from the root of a skybox.
	if (CurrentPortalInSkybox)
		return false;

	for (drawseg_t *seg : portaldrawsegs)
	{
		// ignore segs from other portals
		if (seg->CurrentPortalUniq != CurrentPortalUniq)
			continue;

		// (all checks that are already done in R_CollectPortals have been removed for performance reasons.)

		// don't clip if the sprite is in front of the portal
		if (!P_PointOnLineSidePrecise(spr->gpos.X, spr->gpos.Y, seg->curline->linedef))
			continue;

		// now if current column is covered by this drawseg, we clip it away
		if ((dc_x >= seg->x1) && (dc_x < seg->x2))
			return true;
	}

	return false;
}


//
// R_DrawVisSprite
//	mfloorclip and mceilingclip should also be set.
//
void R_DrawVisSprite (vissprite_t *vis)
{
	fixed_t 		frac;
	FTexture		*tex;
	int				x2, stop4;
	fixed_t			xiscale;
	ESPSResult		mode;
	bool			ispsprite = (!vis->sector && vis->gpos != FVector3(0, 0, 0));

	if (vis->xscale == 0 || fabs(vis->yscale) < (1.0f / 32000.0f))
	{ // scaled to 0; can't see
		return;
	}

	fixed_t centeryfrac = FLOAT2FIXED(CenterY);
	R_SetColorMapLight(vis->Style.BaseColormap, 0, vis->Style.ColormapNum << FRACBITS);

	mode = R_SetPatchStyle (vis->Style.RenderStyle, vis->Style.Alpha, vis->Translation, vis->FillColor);

	if (vis->Style.RenderStyle == LegacyRenderStyles[STYLE_Shaded])
	{ // For shaded sprites, R_SetPatchStyle sets a dc_colormap to an alpha table, but
	  // it is the brightest one. We need to get back to the proper light level for
	  // this sprite.
		R_SetColorMapLight(dc_fcolormap, 0, vis->Style.ColormapNum << FRACBITS);
	}

	if (mode != DontDraw)
	{
		if (mode == DoDraw0)
		{
			// One column at a time
			stop4 = vis->x1;
		}
		else	 // DoDraw1
		{
			// Up to four columns at a time
			stop4 = vis->x2 & ~3;
		}

		tex = vis->pic;
		spryscale = vis->yscale;
		sprflipvert = false;
		dc_iscale = FLOAT2FIXED(1 / vis->yscale);
		frac = vis->startfrac;
		xiscale = vis->xiscale;
		dc_texturemid = vis->texturemid;

		if (vis->renderflags & RF_YFLIP)
		{
			sprflipvert = true;
			spryscale = -spryscale;
			dc_iscale = -dc_iscale;
			dc_texturemid -= vis->pic->GetHeight();
			sprtopscreen = CenterY + dc_texturemid * spryscale;
		}
		else
		{
			sprflipvert = false;
			sprtopscreen = CenterY - dc_texturemid * spryscale;
		}

		dc_x = vis->x1;
		x2 = vis->x2;

		if (dc_x < x2)
		{
			while ((dc_x < stop4) && (dc_x & 3))
			{
				if (ispsprite || !R_ClipSpriteColumnWithPortals(vis))
					R_DrawMaskedColumn (tex, frac, false);
				dc_x++;
				frac += xiscale;
			}

			while (dc_x < stop4)
			{
				rt_initcols(nullptr);
				for (int zz = 4; zz; --zz)
				{
					if (ispsprite || !R_ClipSpriteColumnWithPortals(vis))
						R_DrawMaskedColumn (tex, frac, true);
					dc_x++;
					frac += xiscale;
				}
				rt_draw4cols (dc_x - 4);
			}

			while (dc_x < x2)
			{
				if (ispsprite || !R_ClipSpriteColumnWithPortals(vis))
					R_DrawMaskedColumn (tex, frac, false);
				dc_x++;
				frac += xiscale;
			}
		}
	}

	R_FinishSetPatchStyle ();

	NetUpdate ();
}

void R_DrawWallSprite(vissprite_t *spr)
{
	int x1, x2;
	double iyscale;

	x1 = MAX<int>(spr->x1, spr->wallc.sx1);
	x2 = MIN<int>(spr->x2, spr->wallc.sx2);
	if (x1 >= x2)
		return;
	WallT.InitFromWallCoords(&spr->wallc);
	PrepWall(swall, lwall, spr->pic->GetWidth() << FRACBITS, x1, x2);
	iyscale = 1 / spr->yscale;
	dc_texturemid = (spr->gzt - ViewPos.Z) * iyscale;
	if (spr->renderflags & RF_XFLIP)
	{
		int right = (spr->pic->GetWidth() << FRACBITS) - 1;

		for (int i = x1; i < x2; i++)
		{
			lwall[i] = right - lwall[i];
		}
	}
	// Prepare lighting
	bool calclighting = false;
	FDynamicColormap *usecolormap = basecolormap;
	bool rereadcolormap = true;

	// Decals that are added to the scene must fade to black.
	if (spr->Style.RenderStyle == LegacyRenderStyles[STYLE_Add] && usecolormap->Fade != 0)
	{
		usecolormap = GetSpecialLights(usecolormap->Color, 0, usecolormap->Desaturate);
		rereadcolormap = false;
	}

	int shade = LIGHT2SHADE(spr->sector->lightlevel + r_actualextralight);
	GlobVis = r_WallVisibility;
	rw_lightleft = float (GlobVis / spr->wallc.sz1);
	rw_lightstep = float((GlobVis / spr->wallc.sz2 - rw_lightleft) / (spr->wallc.sx2 - spr->wallc.sx1));
	rw_light = rw_lightleft + (x1 - spr->wallc.sx1) * rw_lightstep;
	if (fixedlightlev >= 0)
		R_SetColorMapLight(usecolormap, 0, FIXEDLIGHT2SHADE(fixedlightlev));
	else if (fixedcolormap != NULL)
		R_SetColorMapLight(fixedcolormap, 0, 0);
	else if (!foggy && (spr->renderflags & RF_FULLBRIGHT))
		R_SetColorMapLight((r_fullbrightignoresectorcolor) ? &FullNormalLight : usecolormap, 0, 0);
	else
		calclighting = true;

	// Draw it
	WallSpriteTile = spr->pic;
	if (spr->renderflags & RF_YFLIP)
	{
		sprflipvert = true;
		iyscale = -iyscale;
		dc_texturemid -= spr->pic->GetHeight();
	}
	else
	{
		sprflipvert = false;
	}

	MaskedScaleY = (float)iyscale;

	dc_x = x1;
	ESPSResult mode;

	mode = R_SetPatchStyle (spr->Style.RenderStyle, spr->Style.Alpha, spr->Translation, spr->FillColor);

	// R_SetPatchStyle can modify basecolormap.
	if (rereadcolormap)
	{
		usecolormap = basecolormap;
	}

	if (mode == DontDraw)
	{
		return;
	}
	else
	{
		int stop4;

		if (mode == DoDraw0)
		{ // 1 column at a time
			stop4 = dc_x;
		}
		else	 // DoDraw1
		{ // up to 4 columns at a time
			stop4 = x2 & ~3;
		}

		while ((dc_x < stop4) && (dc_x & 3))
		{
			if (calclighting)
			{ // calculate lighting
				R_SetColorMapLight(usecolormap, rw_light, shade);
			}
			if (!R_ClipSpriteColumnWithPortals(spr))
				R_WallSpriteColumn(false);
			dc_x++;
		}

		while (dc_x < stop4)
		{
			if (calclighting)
			{ // calculate lighting
				R_SetColorMapLight(usecolormap, rw_light, shade);
			}
			rt_initcols(nullptr);
			for (int zz = 4; zz; --zz)
			{
				if (!R_ClipSpriteColumnWithPortals(spr))
					R_WallSpriteColumn(true);
				dc_x++;
			}
			rt_draw4cols(dc_x - 4);
		}

		while (dc_x < x2)
		{
			if (calclighting)
			{ // calculate lighting
				R_SetColorMapLight(usecolormap, rw_light, shade);
			}
			if (!R_ClipSpriteColumnWithPortals(spr))
				R_WallSpriteColumn(false);
			dc_x++;
		}
	}
	R_FinishSetPatchStyle();
}

void R_WallSpriteColumn (bool useRt)
{
	float iscale = swall[dc_x] * MaskedScaleY;
	dc_iscale = FLOAT2FIXED(iscale);
	spryscale = 1 / iscale;
	if (sprflipvert)
		sprtopscreen = CenterY + dc_texturemid * spryscale;
	else
		sprtopscreen = CenterY - dc_texturemid * spryscale;

	dc_texturefrac = 0;
	R_DrawMaskedColumn(WallSpriteTile, lwall[dc_x], useRt);
	rw_light += rw_lightstep;
}

void R_DrawVisVoxel(vissprite_t *spr, int minslabz, int maxslabz, short *cliptop, short *clipbot)
{
	ESPSResult mode;
	int flags = 0;

	// Do setup for blending.
	R_SetColorMapLight(spr->Style.BaseColormap, 0, spr->Style.ColormapNum << FRACBITS);
	mode = R_SetPatchStyle(spr->Style.RenderStyle, spr->Style.Alpha, spr->Translation, spr->FillColor);

	if (mode == DontDraw)
	{
		return;
	}
	if (colfunc == fuzzcolfunc || colfunc == R_FillColumn)
	{
		flags = DVF_OFFSCREEN | DVF_SPANSONLY;
	}
	else if (colfunc != basecolfunc)
	{
		flags = DVF_OFFSCREEN;
	}
	if (flags != 0)
	{
		R_CheckOffscreenBuffer(RenderTarget->GetWidth(), RenderTarget->GetHeight(), !!(flags & DVF_SPANSONLY));
	}
	if (spr->bInMirror)
	{
		flags |= DVF_MIRRORED;
	}

	// Render the voxel, either directly to the screen or offscreen.
	R_DrawVoxel(spr->pa.vpos, spr->pa.vang, spr->gpos, spr->Angle,
		spr->xscale, FLOAT2FIXED(spr->yscale), spr->voxel, spr->Style.BaseColormap, spr->Style.ColormapNum, cliptop, clipbot,
		minslabz, maxslabz, flags);

	// Blend the voxel, if that's what we need to do.
	if ((flags & ~DVF_MIRRORED) != 0)
	{
		int pixelsize = r_swtruecolor ? 4 : 1;
		for (int x = 0; x < viewwidth; ++x)
		{
			if (!(flags & DVF_SPANSONLY) && (x & 3) == 0)
			{
				rt_initcols(OffscreenColorBuffer + x * OffscreenBufferHeight);
			}
			for (FCoverageBuffer::Span *span = OffscreenCoverageBuffer->Spans[x]; span != NULL; span = span->NextSpan)
			{
				if (flags & DVF_SPANSONLY)
				{
					dc_x = x;
					dc_yl = span->Start;
					dc_yh = span->Stop - 1;
					dc_count = span->Stop - span->Start;
					dc_dest = (ylookup[span->Start] + x) * pixelsize + dc_destorg;
					colfunc();
				}
				else
				{
					rt_span_coverage(x, span->Start, span->Stop - 1);
				}
			}
			if (!(flags & DVF_SPANSONLY) && (x & 3) == 3)
			{
				rt_draw4cols(x - 3);
			}
		}
	}

	R_FinishSetPatchStyle();
	NetUpdate();
}

//
// R_ProjectSprite
// Generates a vissprite for a thing if it might be visible.
//
void R_ProjectSprite (AActor *thing, int fakeside, F3DFloor *fakefloor, F3DFloor *fakeceiling)
{
	double 			tr_x;
	double 			tr_y;
	
	double				gzt;				// killough 3/27/98
	double				gzb;				// [RH] use bottom of sprite, not actor
	double	 			tx;// , tx2;
	double 				tz;

	double 				xscale = 1, yscale = 1;
	
	int 				x1;
	int 				x2;

	FTextureID			picnum;
	FTexture			*tex;
	FVoxelDef			*voxel;
	
	vissprite_t*		vis;
	
	fixed_t 			iscale;

	sector_t*			heightsec;			// killough 3/27/98

	// Don't waste time projecting sprites that are definitely not visible.
	if (thing == NULL ||
		(thing->renderflags & RF_INVISIBLE) ||
		!thing->RenderStyle.IsVisible(thing->Alpha) ||
		!thing->IsVisibleToPlayer() ||
		!thing->IsInsideVisibleAngles())
	{
		return;
	}

	// [ZZ] Or less definitely not visible (hue)
	// [ZZ] 10.01.2016: don't try to clip stuff inside a skybox against the current portal.
	if (!CurrentPortalInSkybox && CurrentPortal && !!P_PointOnLineSidePrecise(thing->Pos(), CurrentPortal->dst))
		return;

	// [RH] Interpolate the sprite's position to make it look smooth
	DVector3 pos = thing->InterpolatedPosition(r_TicFracF);
	pos.Z += thing->GetBobOffset(r_TicFracF);

	tex = NULL;
	voxel = NULL;

	int spritenum = thing->sprite;
	DVector2 spriteScale = thing->Scale;
	int renderflags = thing->renderflags;
	if (spriteScale.Y < 0)
	{
		spriteScale.Y = -spriteScale.Y;
		renderflags ^= RF_YFLIP;
	}
	if (thing->player != NULL)
	{
		P_CheckPlayerSprite(thing, spritenum, spriteScale);
	}

	if (thing->picnum.isValid())
	{
		picnum = thing->picnum;

		tex = TexMan(picnum);
		if (tex->UseType == FTexture::TEX_Null)
		{
			return;
		}

		if (tex->Rotations != 0xFFFF)
		{
			// choose a different rotation based on player view
			spriteframe_t *sprframe = &SpriteFrames[tex->Rotations];
			DAngle ang = (pos - ViewPos).Angle();
			angle_t rot;
			if (sprframe->Texture[0] == sprframe->Texture[1])
			{
				if (thing->flags7 & MF7_SPRITEANGLE)
					rot = (thing->SpriteAngle + 45.0 / 2 * 9).BAMs() >> 28;
				else
					rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + 45.0 / 2 * 9).BAMs() >> 28;
			}
			else
			{
				if (thing->flags7 & MF7_SPRITEANGLE)
					rot = (thing->SpriteAngle + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28;
				else
					rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28;
			}
			picnum = sprframe->Texture[rot];
			if (sprframe->Flip & (1 << rot))
			{
				renderflags ^= RF_XFLIP;
			}
			tex = TexMan[picnum];	// Do not animate the rotation
		}
	}
	else
	{
		// decide which texture to use for the sprite
		if ((unsigned)spritenum >= sprites.Size ())
		{
			DPrintf (DMSG_ERROR, "R_ProjectSprite: invalid sprite number %u\n", spritenum);
			return;
		}
		spritedef_t *sprdef = &sprites[spritenum];
		if (thing->frame >= sprdef->numframes)
		{
			// If there are no frames at all for this sprite, don't draw it.
			return;
		}
		else
		{
			//picnum = SpriteFrames[sprdef->spriteframes + thing->frame].Texture[0];
			// choose a different rotation based on player view
			spriteframe_t *sprframe = &SpriteFrames[sprdef->spriteframes + thing->frame];
			DAngle ang = (pos - ViewPos).Angle();
			angle_t rot;
			if (sprframe->Texture[0] == sprframe->Texture[1])
			{
				if (thing->flags7 & MF7_SPRITEANGLE)
					rot = (thing->SpriteAngle + 45.0 / 2 * 9).BAMs() >> 28;
				else
					rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + 45.0 / 2 * 9).BAMs() >> 28;
			}
			else
			{
				if (thing->flags7 & MF7_SPRITEANGLE)
					rot = (thing->SpriteAngle + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28;
				else
					rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28;
			}
			picnum = sprframe->Texture[rot];
			if (sprframe->Flip & (1 << rot))
			{
				renderflags ^= RF_XFLIP;
			}
			tex = TexMan[picnum];	// Do not animate the rotation
			if (r_drawvoxels)
			{
				voxel = sprframe->Voxel;
			}
		}
	}
	if (spriteScale.X < 0)
	{
		spriteScale.X = -spriteScale.X;
		renderflags ^= RF_XFLIP;
	}
	if (voxel == NULL && (tex == NULL || tex->UseType == FTexture::TEX_Null))
	{
		return;
	}

	if ((renderflags & RF_SPRITETYPEMASK) == RF_WALLSPRITE)
	{
		R_ProjectWallSprite(thing, pos, picnum, spriteScale, renderflags);
		return;
	}

	// transform the origin point
	tr_x = pos.X - ViewPos.X;
	tr_y = pos.Y - ViewPos.Y;

	tz = tr_x * ViewTanCos + tr_y * ViewTanSin;

	// thing is behind view plane?
	if (voxel == NULL && tz < MINZ)
		return;

	tx = tr_x * ViewSin - tr_y * ViewCos;

	// [RH] Flip for mirrors
	if (MirrorFlags & RF_XFLIP)
	{
		tx = -tx;
	}
	//tx2 = tx >> 4;

	// too far off the side?
	// if it's a voxel, it can be further off the side
	if ((voxel == NULL && (fabs(tx / 64) > fabs(tz))) ||
		(voxel != NULL && (fabs(tx / 128) > fabs(tz))))
	{
		return;
	}

	if (voxel == NULL)
	{
		// [RH] Added scaling
		int scaled_to = tex->GetScaledTopOffset();
		int scaled_bo = scaled_to - tex->GetScaledHeight();
		gzt = pos.Z + spriteScale.Y * scaled_to;
		gzb = pos.Z + spriteScale.Y * scaled_bo;
	}
	else
	{
		xscale = spriteScale.X * voxel->Scale;
		yscale = spriteScale.Y * voxel->Scale;
		double piv = voxel->Voxel->Mips[0].Pivot.Z;
		gzt = pos.Z + yscale * piv - thing->Floorclip;
		gzb = pos.Z + yscale * (piv - voxel->Voxel->Mips[0].SizeZ);
		if (gzt <= gzb)
			return;
	}

	// killough 3/27/98: exclude things totally separated
	// from the viewer, by either water or fake ceilings
	// killough 4/11/98: improve sprite clipping for underwater/fake ceilings

	heightsec = thing->Sector->GetHeightSec();

	if (heightsec != NULL)	// only clip things which are in special sectors
	{
		if (fakeside == FAKED_AboveCeiling)
		{
			if (gzt < heightsec->ceilingplane.ZatPoint(pos))
				return;
		}
		else if (fakeside == FAKED_BelowFloor)
		{
			if (gzb >= heightsec->floorplane.ZatPoint(pos))
				return;
		}
		else
		{
			if (gzt < heightsec->floorplane.ZatPoint(pos))
				return;
			if (!(heightsec->MoreFlags & SECF_FAKEFLOORONLY) && gzb >= heightsec->ceilingplane.ZatPoint(pos))
				return;
		}
	}

	if (voxel == NULL)
	{
		xscale = CenterX / tz;

		// [RH] Reject sprites that are off the top or bottom of the screen
		if (globaluclip * tz > ViewPos.Z - gzb || globaldclip * tz < ViewPos.Z - gzt)
		{
			return;
		}

		// [RH] Flip for mirrors
		renderflags ^= MirrorFlags & RF_XFLIP;

		// calculate edges of the shape
		const double thingxscalemul = spriteScale.X / tex->Scale.X;

		tx -= ((renderflags & RF_XFLIP) ? (tex->GetWidth() - tex->LeftOffset - 1) : tex->LeftOffset) * thingxscalemul;
		double dtx1 = tx * xscale;
		x1 = centerx + xs_RoundToInt(dtx1);

		// off the right side?
		if (x1 >= WindowRight)
			return;

		tx += tex->GetWidth() * thingxscalemul;
		x2 = centerx + xs_RoundToInt(tx * xscale);

		// off the left side or too small?
		if ((x2 < WindowLeft || x2 <= x1))
			return;

		xscale = spriteScale.X * xscale / tex->Scale.X;
		iscale = (fixed_t)(FRACUNIT / xscale); // Round towards zero to avoid wrapping in edge cases

		double yscale = spriteScale.Y / tex->Scale.Y;

		// store information in a vissprite
		vis = R_NewVisSprite();

		vis->CurrentPortalUniq = CurrentPortalUniq;
		vis->xscale = FLOAT2FIXED(xscale);
		vis->yscale = float(InvZtoScale * yscale / tz);
		vis->idepth = float(1 / tz);
		vis->floorclip = thing->Floorclip / yscale;
		vis->texturemid = tex->TopOffset - (ViewPos.Z - pos.Z + thing->Floorclip) / yscale;
		vis->x1 = x1 < WindowLeft ? WindowLeft : x1;
		vis->x2 = x2 > WindowRight ? WindowRight : x2;
		vis->Angle = thing->Angles.Yaw;

		if (renderflags & RF_XFLIP)
		{
			vis->startfrac = (tex->GetWidth() << FRACBITS) - 1;
			vis->xiscale = -iscale;
		}
		else
		{
			vis->startfrac = 0;
			vis->xiscale = iscale;
		}

		vis->startfrac += (fixed_t)(vis->xiscale * (vis->x1 - centerx + 0.5 - dtx1));
	}
	else
	{
		vis = R_NewVisSprite();

		vis->CurrentPortalUniq = CurrentPortalUniq;
		vis->xscale = FLOAT2FIXED(xscale);
		vis->yscale = (float)yscale;
		vis->x1 = WindowLeft;
		vis->x2 = WindowRight;
		vis->idepth = 1 / MINZ;
		vis->floorclip = thing->Floorclip;

		pos.Z -= thing->Floorclip;

		vis->Angle = thing->Angles.Yaw + voxel->AngleOffset;

		int voxelspin = (thing->flags & MF_DROPPED) ? voxel->DroppedSpin : voxel->PlacedSpin;
		if (voxelspin != 0)
		{
			DAngle ang = double(I_FPSTime()) * voxelspin / 1000;
			vis->Angle -= ang;
		}

		vis->pa.vpos = { (float)ViewPos.X, (float)ViewPos.Y, (float)ViewPos.Z };
		vis->pa.vang = FAngle((float)ViewAngle.Degrees);
	}

	// killough 3/27/98: save sector for special clipping later
	vis->heightsec = heightsec;
	vis->sector = thing->Sector;

	vis->depth = (float)tz;
	vis->gpos = { (float)pos.X, (float)pos.Y, (float)pos.Z };
	vis->gzb = (float)gzb;		// [RH] use gzb, not thing->z
	vis->gzt = (float)gzt;		// killough 3/27/98
	vis->deltax = float(pos.X - ViewPos.X);
	vis->deltay = float(pos.Y - ViewPos.Y);
	vis->renderflags = renderflags;
	if(thing->flags5 & MF5_BRIGHT)
		vis->renderflags |= RF_FULLBRIGHT; // kg3D
	vis->Style.RenderStyle = thing->RenderStyle;
	vis->FillColor = thing->fillcolor;
	vis->Translation = thing->Translation;		// [RH] thing translation table
	vis->FakeFlatStat = fakeside;
	vis->Style.Alpha = float(thing->Alpha);
	vis->fakefloor = fakefloor;
	vis->fakeceiling = fakeceiling;
	vis->Style.ColormapNum = 0;
	vis->bInMirror = MirrorFlags & RF_XFLIP;
	vis->bSplitSprite = false;

	if (voxel != NULL)
	{
		vis->voxel = voxel->Voxel;
		vis->bIsVoxel = true;
		vis->bWallSprite = false;
		DrewAVoxel = true;
	}
	else
	{
		vis->pic = tex;
		vis->bIsVoxel = false;
		vis->bWallSprite = false;
	}

	// The software renderer cannot invert the source without inverting the overlay
	// too. That means if the source is inverted, we need to do the reverse of what
	// the invert overlay flag says to do.
	INTBOOL invertcolormap = (vis->Style.RenderStyle.Flags & STYLEF_InvertOverlay);

	if (vis->Style.RenderStyle.Flags & STYLEF_InvertSource)
	{
		invertcolormap = !invertcolormap;
	}

	FDynamicColormap *mybasecolormap = basecolormap;

	// Sprites that are added to the scene must fade to black.
	if (vis->Style.RenderStyle == LegacyRenderStyles[STYLE_Add] && mybasecolormap->Fade != 0)
	{
		mybasecolormap = GetSpecialLights(mybasecolormap->Color, 0, mybasecolormap->Desaturate);
	}

	if (vis->Style.RenderStyle.Flags & STYLEF_FadeToBlack)
	{
		if (invertcolormap)
		{ // Fade to white
			mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(255,255,255), mybasecolormap->Desaturate);
			invertcolormap = false;
		}
		else
		{ // Fade to black
			mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(0,0,0), mybasecolormap->Desaturate);
		}
	}

	// get light level
	if (fixedcolormap != NULL)
	{ // fixed map
		vis->Style.BaseColormap = fixedcolormap;
		vis->Style.ColormapNum = 0;
	}
	else
	{
		if (invertcolormap)
		{
			mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate);
		}
		if (fixedlightlev >= 0)
		{
			vis->Style.BaseColormap = mybasecolormap;
			vis->Style.ColormapNum = fixedlightlev >> COLORMAPSHIFT;
		}
		else if (!foggy && ((renderflags & RF_FULLBRIGHT) || (thing->flags5 & MF5_BRIGHT)))
		{ // full bright
			vis->Style.BaseColormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight : mybasecolormap;
			vis->Style.ColormapNum = 0;
		}
		else
		{ // diminished light
			vis->Style.ColormapNum = GETPALOOKUP(
				r_SpriteVisibility / MAX(tz, MINZ), spriteshade);
			vis->Style.BaseColormap = mybasecolormap;
		}
	}
}

static void R_ProjectWallSprite(AActor *thing, const DVector3 &pos, FTextureID picnum, const DVector2 &scale, int renderflags)
{
	FWallCoords wallc;
	double x1, x2;
	DVector2 left, right;
	double gzb, gzt, tz;
	FTexture *pic = TexMan(picnum, true);
	DAngle ang = thing->Angles.Yaw + 90;
	double angcos = ang.Cos();
	double angsin = ang.Sin();
	vissprite_t *vis;

	// Determine left and right edges of sprite. The sprite's angle is its normal,
	// so the edges are 90 degrees each side of it.
	x2 = pic->GetScaledWidth();
	x1 = pic->GetScaledLeftOffset();

	x1 *= scale.X;
	x2 *= scale.X;

	left.X = pos.X - x1 * angcos - ViewPos.X;
	left.Y = pos.Y - x1 * angsin - ViewPos.Y;
	right.X = left.X + x2 * angcos;
	right.Y = right.Y + x2 * angsin;

	// Is it off-screen?
	if (wallc.Init(left, right, TOO_CLOSE_Z))
		return;
	
	if (wallc.sx1 >= WindowRight || wallc.sx2 <= WindowLeft)
		return;

	// Sprite sorting should probably treat these as walls, not sprites,
	// but right now, I just want to get them drawing.
	tz = (pos.X - ViewPos.X) * ViewTanCos + (pos.Y - ViewPos.Y) * ViewTanSin;

	int scaled_to = pic->GetScaledTopOffset();
	int scaled_bo = scaled_to - pic->GetScaledHeight();
	gzt = pos.Z + scale.Y * scaled_to;
	gzb = pos.Z + scale.Y * scaled_bo;

	vis = R_NewVisSprite();
	vis->CurrentPortalUniq = CurrentPortalUniq;
	vis->x1 = wallc.sx1 < WindowLeft ? WindowLeft : wallc.sx1;
	vis->x2 = wallc.sx2 >= WindowRight ? WindowRight : wallc.sx2;
	vis->yscale = (float)scale.Y;
	vis->idepth = float(1 / tz);
	vis->depth = (float)tz;
	vis->sector = thing->Sector;
	vis->heightsec = NULL;
	vis->gpos = { (float)pos.X, (float)pos.Y, (float)pos.Z };
	vis->gzb = (float)gzb;
	vis->gzt = (float)gzt;
	vis->deltax = float(pos.X - ViewPos.X);
	vis->deltay = float(pos.Y - ViewPos.Y);
	vis->renderflags = renderflags;
	if(thing->flags5 & MF5_BRIGHT) vis->renderflags |= RF_FULLBRIGHT; // kg3D
	vis->Style.RenderStyle = thing->RenderStyle;
	vis->FillColor = thing->fillcolor;
	vis->Translation = thing->Translation;
	vis->FakeFlatStat = 0;
	vis->Style.Alpha = float(thing->Alpha);
	vis->fakefloor = NULL;
	vis->fakeceiling = NULL;
	vis->bInMirror = MirrorFlags & RF_XFLIP;
	vis->pic = pic;
	vis->bIsVoxel = false;
	vis->bWallSprite = true;
	vis->Style.ColormapNum = GETPALOOKUP(
		r_SpriteVisibility / MAX(tz, MINZ), spriteshade);
	vis->Style.BaseColormap = basecolormap;
	vis->wallc = wallc;
}

//
// R_AddSprites
// During BSP traversal, this adds sprites by sector.
//
// killough 9/18/98: add lightlevel as parameter, fixing underwater lighting
// [RH] Save which side of heightsec sprite is on here.
void R_AddSprites (sector_t *sec, int lightlevel, int fakeside)
{
	AActor *thing;
	F3DFloor *fakeceiling = NULL;
	F3DFloor *fakefloor = NULL;

	// BSP is traversed by subsector.
	// A sector might have been split into several
	//	subsectors during BSP building.
	// Thus we check whether it was already added.
	if (sec->thinglist == NULL || sec->validcount == validcount)
		return;

	// Well, now it will be done.
	sec->validcount = validcount;

	spriteshade = LIGHT2SHADE(lightlevel + r_actualextralight);

	// Handle all things in sector.
	for (thing = sec->thinglist; thing; thing = thing->snext)
	{
		FIntCVar *cvar = thing->GetClass()->distancecheck;
		if (cvar != NULL && *cvar >= 0)
		{
			double dist = (thing->Pos() - ViewPos).LengthSquared();
			double check = (double)**cvar;
			if (dist >= check * check)
			{
				continue;
			}
		}

		// find fake level
		for(auto rover : frontsector->e->XFloor.ffloors) 
		{
			if(!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERPLANES)) continue;
			if(!(rover->flags & FF_SOLID) || rover->alpha != 255) continue;
			if(!fakefloor)
			{
				if(!rover->top.plane->isSlope())
				{
					if(rover->top.plane->ZatPoint(0., 0.) <= thing->Z()) fakefloor = rover;
				}
			}
			if(!rover->bottom.plane->isSlope())
			{
				if(rover->bottom.plane->ZatPoint(0., 0.) >= thing->Top()) fakeceiling = rover;
			}
		}	
		R_ProjectSprite (thing, fakeside, fakefloor, fakeceiling);
		fakeceiling = NULL;
		fakefloor = NULL;
	}
}

//
// R_DrawPSprite
//
void R_DrawPSprite(DPSprite *pspr, AActor *owner, float bobx, float boby, double wx, double wy, double ticfrac)
{
	double 				tx;
	int 				x1;
	int 				x2;
	double				sx, sy;
	spritedef_t*		sprdef;
	spriteframe_t*		sprframe;
	FTextureID			picnum;
	WORD				flip;
	FTexture*			tex;
	vissprite_t*		vis;
	bool				noaccel;
	static TArray<vissprite_t> avis;

	if (avis.Size() < vispspindex + 1)
		avis.Reserve(avis.Size() - vispspindex + 1);

	// decide which patch to use
	if ((unsigned)pspr->GetSprite() >= (unsigned)sprites.Size())
	{
		DPrintf(DMSG_ERROR, "R_DrawPSprite: invalid sprite number %i\n", pspr->GetSprite());
		return;
	}
	sprdef = &sprites[pspr->GetSprite()];
	if (pspr->GetFrame() >= sprdef->numframes)
	{
		DPrintf(DMSG_ERROR, "R_DrawPSprite: invalid sprite frame %i : %i\n", pspr->GetSprite(), pspr->GetFrame());
		return;
	}
	sprframe = &SpriteFrames[sprdef->spriteframes + pspr->GetFrame()];

	picnum = sprframe->Texture[0];
	flip = sprframe->Flip & 1;
	tex = TexMan(picnum);

	if (tex->UseType == FTexture::TEX_Null)
		return;

	if (pspr->firstTic)
	{ // Can't interpolate the first tic.
		pspr->firstTic = false;
		pspr->oldx = pspr->x;
		pspr->oldy = pspr->y;
	}

	sx = pspr->oldx + (pspr->x - pspr->oldx) * ticfrac;
	sy = pspr->oldy + (pspr->y - pspr->oldy) * ticfrac + WEAPON_FUDGE_Y;

	if (pspr->Flags & PSPF_ADDBOB)
	{
		sx += bobx;
		sy += boby;
	}

	if (pspr->Flags & PSPF_ADDWEAPON && pspr->GetID() != PSP_WEAPON)
	{
		sx += wx;
		sy += wy;
	}

	// calculate edges of the shape
	tx = sx - BASEXCENTER;

	tx -= tex->GetScaledLeftOffset();
	x1 = xs_RoundToInt(CenterX + tx * pspritexscale);

	// off the right side
	if (x1 > viewwidth)
		return;

	tx += tex->GetScaledWidth();
	x2 = xs_RoundToInt(CenterX + tx * pspritexscale);

	// off the left side
	if (x2 <= 0)
		return;

	// store information in a vissprite
	vis = &avis[vispspindex];
	vis->renderflags = owner->renderflags;
	vis->floorclip = 0;

	vis->texturemid = (BASEYCENTER - sy) * tex->Scale.Y + tex->TopOffset;

	if (camera->player && (RenderTarget != screen ||
		viewheight == RenderTarget->GetHeight() ||
		(RenderTarget->GetWidth() > (BASEXCENTER * 2) && !st_scale)))
	{	// Adjust PSprite for fullscreen views
		AWeapon *weapon = dyn_cast<AWeapon>(pspr->GetCaller());
		if (weapon != nullptr && weapon->YAdjust != 0)
		{
			if (RenderTarget != screen || viewheight == RenderTarget->GetHeight())
			{
				vis->texturemid -= weapon->YAdjust;
			}
			else
			{
				vis->texturemid -= StatusBar->GetDisplacement() * weapon->YAdjust;
			}
		}
	}
	if (pspr->GetID() < PSP_TARGETCENTER)
	{ // Move the weapon down for 1280x1024.
		vis->texturemid -= AspectPspriteOffset(WidescreenRatio);
	}
	vis->x1 = x1 < 0 ? 0 : x1;
	vis->x2 = x2 >= viewwidth ? viewwidth : x2;
	vis->xscale = FLOAT2FIXED(pspritexscale / tex->Scale.X);
	vis->yscale = float(pspriteyscale / tex->Scale.Y);
	vis->Translation = 0;		// [RH] Use default colors
	vis->pic = tex;
	vis->Style.ColormapNum = 0;

	if (!(flip) != !(pspr->Flags & PSPF_FLIP))
	{
		vis->xiscale = -FLOAT2FIXED(pspritexiscale * tex->Scale.X);
		vis->startfrac = (tex->GetWidth() << FRACBITS) - 1;
	}
	else
	{
		vis->xiscale = FLOAT2FIXED(pspritexiscale * tex->Scale.X);
		vis->startfrac = 0;
	}

	if (vis->x1 > x1)
		vis->startfrac += vis->xiscale*(vis->x1 - x1);

	noaccel = false;
	FDynamicColormap *colormap_to_use = nullptr;
	if (pspr->GetID() < PSP_TARGETCENTER)
	{
		vis->Style.Alpha = float(owner->Alpha);
		vis->Style.RenderStyle = owner->RenderStyle;

		// The software renderer cannot invert the source without inverting the overlay
		// too. That means if the source is inverted, we need to do the reverse of what
		// the invert overlay flag says to do.
		INTBOOL invertcolormap = (vis->Style.RenderStyle.Flags & STYLEF_InvertOverlay);

		if (vis->Style.RenderStyle.Flags & STYLEF_InvertSource)
		{
			invertcolormap = !invertcolormap;
		}

		FDynamicColormap *mybasecolormap = basecolormap;

		if (vis->Style.RenderStyle.Flags & STYLEF_FadeToBlack)
		{
			if (invertcolormap)
			{ // Fade to white
				mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(255, 255, 255), mybasecolormap->Desaturate);
				invertcolormap = false;
			}
			else
			{ // Fade to black
				mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(0, 0, 0), mybasecolormap->Desaturate);
			}
		}

		if (realfixedcolormap != nullptr && (!r_swtruecolor || (r_shadercolormaps && screen->Accel2D)))
		{ // fixed color
			vis->Style.BaseColormap = realfixedcolormap;
			vis->Style.ColormapNum = 0;
		}
		else
		{
			if (invertcolormap)
			{
				mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate);
			}
			if (fixedlightlev >= 0)
			{
				vis->Style.BaseColormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight : mybasecolormap;
				vis->Style.ColormapNum = fixedlightlev >> COLORMAPSHIFT;
			}
			else if (!foggy && pspr->GetState()->GetFullbright())
			{ // full bright
				vis->Style.BaseColormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight : mybasecolormap;	// [RH] use basecolormap
				vis->Style.ColormapNum = 0;
			}
			else
			{ // local light
				vis->Style.BaseColormap = mybasecolormap;
				vis->Style.ColormapNum = GETPALOOKUP(0, spriteshade);
			}
		}
		if (camera->Inventory != nullptr)
		{
			BYTE oldcolormapnum = vis->Style.ColormapNum;
			FSWColormap *oldcolormap = vis->Style.BaseColormap;
			camera->Inventory->AlterWeaponSprite (&vis->Style);
			if (vis->Style.BaseColormap != oldcolormap || vis->Style.ColormapNum != oldcolormapnum)
			{
				// The colormap has changed. Is it one we can easily identify?
				// If not, then don't bother trying to identify it for
				// hardware accelerated drawing.
				if (vis->Style.BaseColormap < &SpecialColormaps[0] ||
					vis->Style.BaseColormap > &SpecialColormaps.Last())
				{
					noaccel = true;
				}
				// Has the basecolormap changed? If so, we can't hardware accelerate it,
				// since we don't know what it is anymore.
				else if (vis->Style.BaseColormap != mybasecolormap)
				{
					noaccel = true;
				}
			}
		}
		// If we're drawing with a special colormap, but shaders for them are disabled, do
		// not accelerate.
		if (!r_shadercolormaps && (vis->Style.BaseColormap >= &SpecialColormaps[0] &&
			vis->Style.BaseColormap <= &SpecialColormaps.Last()))
		{
			noaccel = true;
		}
		// If drawing with a BOOM colormap, disable acceleration.
		if (mybasecolormap == &NormalLight && NormalLight.Maps != realcolormaps.Maps)
		{
			noaccel = true;
		}
		// If the main colormap has fixed lights, and this sprite is being drawn with that
		// colormap, disable acceleration so that the lights can remain fixed.
		if (!noaccel && realfixedcolormap == nullptr &&
			NormalLightHasFixedLights && mybasecolormap == &NormalLight &&
			vis->pic->UseBasePalette())
		{
			noaccel = true;
		}
		// [SP] If emulating GZDoom fullbright, disable acceleration
		if (r_fullbrightignoresectorcolor && fixedlightlev >= 0)
			mybasecolormap = &FullNormalLight;
		if (r_fullbrightignoresectorcolor && !foggy && pspr->GetState()->GetFullbright())
			mybasecolormap = &FullNormalLight;
		colormap_to_use = mybasecolormap;
	}
	else
	{
		colormap_to_use = basecolormap;
		vis->Style.BaseColormap = basecolormap;
		vis->Style.ColormapNum = 0;
		vis->Style.RenderStyle = STYLE_Normal;
	}

	// Check for hardware-assisted 2D. If it's available, and this sprite is not
	// fuzzy, don't draw it until after the switch to 2D mode.
	if (!noaccel && RenderTarget == screen && (DFrameBuffer *)screen->Accel2D)
	{
		FRenderStyle style = vis->Style.RenderStyle;
		style.CheckFuzz();
		if (style.BlendOp != STYLEOP_Fuzz)
		{
			if (vispsprites.Size() < vispspindex + 1)
				vispsprites.Reserve(vispsprites.Size() - vispspindex + 1);

			vispsprites[vispspindex].vis = vis;
			vispsprites[vispspindex].basecolormap = colormap_to_use;
			vispsprites[vispspindex].x1 = x1;
			vispspindex++;
			return;
		}
	}
	R_DrawVisSprite(vis);
}

//==========================================================================
//
// R_DrawPlayerSprites
//
//==========================================================================

void R_DrawPlayerSprites ()
{
	int 		i;
	int 		lightnum;
	DPSprite*	psp;
	DPSprite*	weapon;
	sector_t*	sec = NULL;
	static sector_t tempsec;
	int			floorlight, ceilinglight;
	F3DFloor *rover;

	if (!r_drawplayersprites ||
		!camera ||
		!camera->player ||
		(players[consoleplayer].cheats & CF_CHASECAM) ||
		(r_deathcamera && camera->health <= 0))
		return;

	if (fixedlightlev < 0 && viewsector->e && viewsector->e->XFloor.lightlist.Size())
	{
		for (i = viewsector->e->XFloor.lightlist.Size() - 1; i >= 0; i--)
		{
			if (ViewPos.Z <= viewsector->e->XFloor.lightlist[i].plane.Zat0())
			{
				rover = viewsector->e->XFloor.lightlist[i].caster;
				if (rover)
				{
					if (rover->flags & FF_DOUBLESHADOW && ViewPos.Z <= rover->bottom.plane->Zat0())
						break;
					sec = rover->model;
					if (rover->flags & FF_FADEWALLS)
						basecolormap = sec->ColorMap;
					else
						basecolormap = viewsector->e->XFloor.lightlist[i].extra_colormap;
				}
				break;
			}
		}
		if(!sec)
		{
			sec = viewsector;
			basecolormap = sec->ColorMap;
		}
		floorlight = ceilinglight = sec->lightlevel;
	}
	else
	{	// This used to use camera->Sector but due to interpolation that can be incorrect
		// when the interpolated viewpoint is in a different sector than the camera.
		sec = R_FakeFlat (viewsector, &tempsec, &floorlight,
			&ceilinglight, false);

		// [RH] set basecolormap
		basecolormap = sec->ColorMap;
	}

	// [RH] set foggy flag
	foggy = (level.fadeto || basecolormap->Fade || (level.flags & LEVEL_HASFADETABLE));
	r_actualextralight = foggy ? 0 : extralight << 4;

	// get light level
	lightnum = ((floorlight + ceilinglight) >> 1) + r_actualextralight;
	spriteshade = LIGHT2SHADE(lightnum) - 24*FRACUNIT;

	// clip to screen bounds
	mfloorclip = screenheightarray;
	mceilingclip = zeroarray;

	if (camera->player != NULL)
	{
		double centerhack = CenterY;
		double wx, wy;
		float bobx, boby;

		CenterY = viewheight / 2;

		P_BobWeapon (camera->player, &bobx, &boby, r_TicFracF);

		// Interpolate the main weapon layer once so as to be able to add it to other layers.
		if ((weapon = camera->player->FindPSprite(PSP_WEAPON)) != nullptr)
		{
			if (weapon->firstTic)
			{
				wx = weapon->x;
				wy = weapon->y;
			}
			else
			{
				wx = weapon->oldx + (weapon->x - weapon->oldx) * r_TicFracF;
				wy = weapon->oldy + (weapon->y - weapon->oldy) * r_TicFracF;
			}
		}
		else
		{
			wx = 0;
			wy = 0;
		}

		// add all active psprites
		psp = camera->player->psprites;
		while (psp)
		{
			// [RH] Don't draw the targeter's crosshair if the player already has a crosshair set.
			// It's possible this psprite's caller is now null but the layer itself hasn't been destroyed
			// because it didn't tick yet (if we typed 'take all' while in the console for example).
			// In this case let's simply not draw it to avoid crashing.
			if ((psp->GetID() != PSP_TARGETCENTER || CrosshairImage == nullptr) && psp->GetCaller() != nullptr)
			{
				R_DrawPSprite(psp, camera, bobx, boby, wx, wy, r_TicFracF);
			}

			psp = psp->GetNext();
		}

		CenterY = centerhack;
	}
}

//==========================================================================
//
// R_DrawRemainingPlayerSprites
//
// Called from D_Display to draw sprites that were not drawn by
// R_DrawPlayerSprites().
//
//==========================================================================

void R_DrawRemainingPlayerSprites()
{
	for (unsigned int i = 0; i < vispspindex; i++)
	{
		vissprite_t *vis;
		
		vis = vispsprites[i].vis;
		FDynamicColormap *colormap = vispsprites[i].basecolormap;
		bool flip = vis->xiscale < 0;
		FSpecialColormap *special = NULL;
		PalEntry overlay = 0;
		FColormapStyle colormapstyle;
		bool usecolormapstyle = false;

		if (vis->Style.BaseColormap >= &SpecialColormaps[0] && 
			vis->Style.BaseColormap < &SpecialColormaps[SpecialColormaps.Size()])
		{
			special = static_cast<FSpecialColormap*>(vis->Style.BaseColormap);
		}
		else if (colormap->Color == PalEntry(255,255,255) &&
			colormap->Desaturate == 0)
		{
			overlay = colormap->Fade;
			overlay.a = BYTE(vis->Style.ColormapNum * 255 / NUMCOLORMAPS);
		}
		else
		{
			usecolormapstyle = true;
			colormapstyle.Color = colormap->Color;
			colormapstyle.Fade = colormap->Fade;
			colormapstyle.Desaturate = colormap->Desaturate;
			colormapstyle.FadeLevel = vis->Style.ColormapNum / float(NUMCOLORMAPS);
		}
		screen->DrawTexture(vis->pic,
			viewwindowx + vispsprites[i].x1,
			viewwindowy + viewheight/2 - vis->texturemid * vis->yscale - 0.5,
			DTA_DestWidthF, FIXED2DBL(vis->pic->GetWidth() * vis->xscale),
			DTA_DestHeightF, vis->pic->GetHeight() * vis->yscale,
			DTA_Translation, TranslationToTable(vis->Translation),
			DTA_FlipX, flip,
			DTA_TopOffset, 0,
			DTA_LeftOffset, 0,
			DTA_ClipLeft, viewwindowx,
			DTA_ClipTop, viewwindowy,
			DTA_ClipRight, viewwindowx + viewwidth,
			DTA_ClipBottom, viewwindowy + viewheight,
			DTA_AlphaF, vis->Style.Alpha,
			DTA_RenderStyle, vis->Style.RenderStyle,
			DTA_FillColor, vis->FillColor,
			DTA_SpecialColormap, special,
			DTA_ColorOverlay, overlay.d,
			DTA_ColormapStyle, usecolormapstyle ? &colormapstyle : NULL,
			TAG_DONE);
	}

	vispspindex = 0;
}

//
// R_SortVisSprites
//
// [RH] The old code for this function used a bubble sort, which was far less
//		than optimal with large numbers of sprites. I changed it to use the
//		stdlib qsort() function instead, and now it is a *lot* faster; the
//		more vissprites that need to be sorted, the better the performance
//		gain compared to the old function.
//
// Sort vissprites by depth, far to near

// This is the standard version, which does a simple test based on depth.
static bool sv_compare(vissprite_t *a, vissprite_t *b)
{
	return a->idepth > b->idepth;
}

// This is an alternate version, for when one or more voxel is in view.
// It does a 2D distance test based on whichever one is furthest from
// the viewpoint.
static bool sv_compare2d(vissprite_t *a, vissprite_t *b)
{
	return DVector2(a->deltax, a->deltay).LengthSquared() <
		   DVector2(b->deltax, b->deltay).LengthSquared();
}

#if 0
static drawseg_t **drawsegsorter;
static int drawsegsortersize = 0;

// Sort vissprites by leftmost column, left to right
static int sv_comparex (const void *arg1, const void *arg2)
{
	return (*(vissprite_t **)arg2)->x1 - (*(vissprite_t **)arg1)->x1;
}

// Sort drawsegs by rightmost column, left to right
static int sd_comparex (const void *arg1, const void *arg2)
{
	return (*(drawseg_t **)arg2)->x2 - (*(drawseg_t **)arg1)->x2;
}

// Split up vissprites that intersect drawsegs
void R_SplitVisSprites ()
{
	size_t start, stop;
	size_t numdrawsegs = ds_p - firstdrawseg;
	size_t numsprites;
	size_t spr, dseg, dseg2;

	if (!r_splitsprites)
		return;

	if (numdrawsegs == 0 || vissprite_p - firstvissprite == 0)
		return;

	// Sort drawsegs from left to right
	if (numdrawsegs > drawsegsortersize)
	{
		if (drawsegsorter != NULL)
			delete[] drawsegsorter;
		drawsegsortersize = numdrawsegs * 2;
		drawsegsorter = new drawseg_t *[drawsegsortersize];
	}
	for (dseg = dseg2 = 0; dseg < numdrawsegs; ++dseg)
	{
		// Drawsegs that don't clip any sprites don't need to be considered.
		if (firstdrawseg[dseg].silhouette)
		{
			drawsegsorter[dseg2++] = &firstdrawseg[dseg];
		}
	}
	numdrawsegs = dseg2;
	if (numdrawsegs == 0)
	{
		return;
	}
	qsort (drawsegsorter, numdrawsegs, sizeof(drawseg_t *), sd_comparex);

	// Now sort vissprites from left to right, and walk them simultaneously
	// with the drawsegs, splitting any that intersect.
	start = firstvissprite - vissprites;

	int p = 0;
	do
	{
		p++;
		R_SortVisSprites (sv_comparex, start);
		stop = vissprite_p - vissprites;
		numsprites = stop - start;

		spr = dseg = 0;
		do
		{
			vissprite_t *vis = spritesorter[spr], *vis2;

			// Skip drawsegs until we get to one that doesn't end before the sprite
			// begins.
			while (dseg < numdrawsegs && drawsegsorter[dseg]->x2 <= vis->x1)
			{
				dseg++;
			}
			// Now split the sprite against any drawsegs it intersects
			for (dseg2 = dseg; dseg2 < numdrawsegs; dseg2++)
			{
				drawseg_t *ds = drawsegsorter[dseg2];

				if (ds->x1 > vis->x2 || ds->x2 < vis->x1)
					continue;

				if ((vis->idepth < ds->siz1) != (vis->idepth < ds->siz2))
				{ // The drawseg is crossed; find the x where the intersection occurs
					int cross = Scale (vis->idepth - ds->siz1, ds->sx2 - ds->sx1, ds->siz2 - ds->siz1) + ds->sx1 + 1;

/*					if (cross < ds->x1 || cross > ds->x2)
					{ // The original seg is crossed, but the drawseg is not
						continue;
					}
*/					if (cross <= vis->x1 || cross >= vis->x2)
					{ // Don't create 0-sized sprites
						continue;
					}

					vis->bSplitSprite = true;

					// Create a new vissprite for the right part of the sprite
					vis2 = R_NewVisSprite ();
					*vis2 = *vis;
					vis2->startfrac += vis2->xiscale * (cross - vis2->x1);
					vis->x2 = cross-1;
					vis2->x1 = cross;
					//vis2->alpha /= 2;
					//vis2->RenderStyle = STYLE_Add;

					if (vis->idepth < ds->siz1)
					{ // Left is in back, right is in front
						vis->sector  = ds->curline->backsector;
						vis2->sector = ds->curline->frontsector;
					}
					else
					{ // Right is in front, left is in back
						vis->sector  = ds->curline->frontsector;
						vis2->sector = ds->curline->backsector;
					}
				}
			}
		}
		while (dseg < numdrawsegs && ++spr < numsprites);

		// Repeat for any new sprites that were added.
	}
	while (start = stop, stop != vissprite_p - vissprites);
}
#endif

#ifdef __GNUC__
static void swap(vissprite_t *&a, vissprite_t *&b)
{
	vissprite_t *t = a;
	a = b;
	b = t;
}
#endif

void R_SortVisSprites (bool (*compare)(vissprite_t *, vissprite_t *), size_t first)
{
	int i;
	vissprite_t **spr;

	vsprcount = int(vissprite_p - &vissprites[first]);

	if (vsprcount == 0)
		return;

	if (spritesortersize < MaxVisSprites)
	{
		if (spritesorter != NULL)
			delete[] spritesorter;
		spritesorter = new vissprite_t *[MaxVisSprites];
		spritesortersize = MaxVisSprites;
	}

	if (!(i_compatflags & COMPATF_SPRITESORT))
	{
		for (i = 0, spr = firstvissprite; i < vsprcount; i++, spr++)
		{
			spritesorter[i] = *spr;
		}
	}
	else
	{
		// If the compatibility option is on sprites of equal distance need to
		// be sorted in inverse order. This is most easily achieved by
		// filling the sort array backwards before the sort.
		for (i = 0, spr = firstvissprite + vsprcount-1; i < vsprcount; i++, spr--)
		{
			spritesorter[i] = *spr;
		}
	}

	std::stable_sort(&spritesorter[0], &spritesorter[vsprcount], compare);
}

//
// R_DrawSprite
//
void R_DrawSprite (vissprite_t *spr)
{
	static short clipbot[MAXWIDTH];
	static short cliptop[MAXWIDTH];
	drawseg_t *ds;
	int i;
	int x1, x2;
	int r1, r2;
	short topclip, botclip;
	short *clip1, *clip2;
	FSWColormap *colormap = spr->Style.BaseColormap;
	int colormapnum = spr->Style.ColormapNum;
	F3DFloor *rover;
	FDynamicColormap *mybasecolormap;

	// [RH] Check for particles
	if (!spr->bIsVoxel && spr->pic == NULL)
	{
		// kg3D - reject invisible parts
		if ((fake3D & FAKE3D_CLIPBOTTOM) && spr->gpos.Z <= sclipBottom) return;
		if ((fake3D & FAKE3D_CLIPTOP)    && spr->gpos.Z >= sclipTop) return;
		R_DrawParticle (spr);
		return;
	}

	x1 = spr->x1;
	x2 = spr->x2;

	// [RH] Quickly reject sprites with bad x ranges.
	if (x1 >= x2)
		return;

	// [RH] Sprites split behind a one-sided line can also be discarded.
	if (spr->sector == NULL)
		return;

	// kg3D - reject invisible parts
	if ((fake3D & FAKE3D_CLIPBOTTOM) && spr->gzt <= sclipBottom) return;
	if ((fake3D & FAKE3D_CLIPTOP)    && spr->gzb >= sclipTop) return;

	// kg3D - correct colors now
	if (!fixedcolormap && fixedlightlev < 0 && spr->sector->e && spr->sector->e->XFloor.lightlist.Size()) 
	{
		if (!(fake3D & FAKE3D_CLIPTOP))
		{
			sclipTop = spr->sector->ceilingplane.ZatPoint(ViewPos);
		}
		sector_t *sec = NULL;
		for (i = spr->sector->e->XFloor.lightlist.Size() - 1; i >= 0; i--)
		{
			if (sclipTop <= spr->sector->e->XFloor.lightlist[i].plane.Zat0()) 
			{
				rover = spr->sector->e->XFloor.lightlist[i].caster;
				if (rover) 
				{
					if (rover->flags & FF_DOUBLESHADOW && sclipTop <= rover->bottom.plane->Zat0())
					{
						break;
					}
					sec = rover->model;
					if (rover->flags & FF_FADEWALLS)
					{
						mybasecolormap = sec->ColorMap;
					}
					else
					{
						mybasecolormap = spr->sector->e->XFloor.lightlist[i].extra_colormap;
					}
				}
				break;
			}
		}
		// found new values, recalculate
		if (sec) 
		{
			INTBOOL invertcolormap = (spr->Style.RenderStyle.Flags & STYLEF_InvertOverlay);

			if (spr->Style.RenderStyle.Flags & STYLEF_InvertSource)
			{
				invertcolormap = !invertcolormap;
			}

			// Sprites that are added to the scene must fade to black.
			if (spr->Style.RenderStyle == LegacyRenderStyles[STYLE_Add] && mybasecolormap->Fade != 0)
			{
				mybasecolormap = GetSpecialLights(mybasecolormap->Color, 0, mybasecolormap->Desaturate);
			}

			if (spr->Style.RenderStyle.Flags & STYLEF_FadeToBlack)
			{
				if (invertcolormap)
				{ // Fade to white
					mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(255,255,255), mybasecolormap->Desaturate);
					invertcolormap = false;
				}
				else
				{ // Fade to black
					mybasecolormap = GetSpecialLights(mybasecolormap->Color, MAKERGB(0,0,0), mybasecolormap->Desaturate);
				}
			}

			// get light level
			if (invertcolormap)
			{
				mybasecolormap = GetSpecialLights(mybasecolormap->Color, mybasecolormap->Fade.InverseColor(), mybasecolormap->Desaturate);
			}
			if (fixedlightlev >= 0)
			{
				spr->Style.BaseColormap = mybasecolormap;
				spr->Style.ColormapNum = fixedlightlev >> COLORMAPSHIFT;
			}
			else if (!foggy && (spr->renderflags & RF_FULLBRIGHT))
			{ // full bright
				spr->Style.BaseColormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight : mybasecolormap;
				spr->Style.ColormapNum = 0;
			}
			else
			{ // diminished light
				spriteshade = LIGHT2SHADE(sec->lightlevel + r_actualextralight);
				spr->Style.BaseColormap = mybasecolormap;
				spr->Style.ColormapNum = GETPALOOKUP(r_SpriteVisibility / MAX(MINZ, (double)spr->depth), spriteshade);
			}
		}
	}

	// [RH] Initialize the clipping arrays to their largest possible range
	// instead of using a special "not clipped" value. This eliminates
	// visual anomalies when looking down and should be faster, too.
	topclip = 0;
	botclip = viewheight;

	// killough 3/27/98:
	// Clip the sprite against deep water and/or fake ceilings.
	// [RH] rewrote this to be based on which part of the sector is really visible

	double scale = InvZtoScale * spr->idepth;
	double hzb = DBL_MIN, hzt = DBL_MAX;

	if (spr->bIsVoxel && spr->floorclip != 0)
	{
		hzb = spr->gzb;
	}

	if (spr->heightsec && !(spr->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC))
	{ // only things in specially marked sectors
		if (spr->FakeFlatStat != FAKED_AboveCeiling)
		{
			double hz = spr->heightsec->floorplane.ZatPoint(spr->gpos);
			int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale);

			if (spr->FakeFlatStat == FAKED_BelowFloor)
			{ // seen below floor: clip top
				if (!spr->bIsVoxel && h > topclip)
				{
					topclip = short(MIN(h, viewheight));
				}
				hzt = MIN(hzt, hz);
			}
			else
			{ // seen in the middle: clip bottom
				if (!spr->bIsVoxel && h < botclip)
				{
					botclip = MAX<short> (0, h);
				}
				hzb = MAX(hzb, hz);
			}
		}
		if (spr->FakeFlatStat != FAKED_BelowFloor && !(spr->heightsec->MoreFlags & SECF_FAKEFLOORONLY))
		{
			double hz = spr->heightsec->ceilingplane.ZatPoint(spr->gpos);
			int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale);

			if (spr->FakeFlatStat == FAKED_AboveCeiling)
			{ // seen above ceiling: clip bottom
				if (!spr->bIsVoxel && h < botclip)
				{
					botclip = MAX<short> (0, h);
				}
				hzb = MAX(hzb, hz);
			}
			else
			{ // seen in the middle: clip top
				if (!spr->bIsVoxel && h > topclip)
				{
					topclip = MIN(h, viewheight);
				}
				hzt = MIN(hzt, hz);
			}
		}
	}
	// killough 3/27/98: end special clipping for deep water / fake ceilings
	else if (!spr->bIsVoxel && spr->floorclip)
	{ // [RH] Move floorclip stuff from R_DrawVisSprite to here
		//int clip = ((FLOAT2FIXED(CenterY) - FixedMul (spr->texturemid - (spr->pic->GetHeight() << FRACBITS) + spr->floorclip, spr->yscale)) >> FRACBITS);
		int clip = xs_RoundToInt(CenterY - (spr->texturemid - spr->pic->GetHeight() + spr->floorclip) * spr->yscale);
		if (clip < botclip)
		{
			botclip = MAX<short>(0, clip);
		}
	}

	if (fake3D & FAKE3D_CLIPBOTTOM)
	{
		if (!spr->bIsVoxel)
		{
			double hz = sclipBottom;
			if (spr->fakefloor)
			{
				double floorz = spr->fakefloor->top.plane->Zat0();
				if (ViewPos.Z > floorz && floorz == sclipBottom )
				{
					hz = spr->fakefloor->bottom.plane->Zat0();
				}
			}
			int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale);
			if (h < botclip)
			{
				botclip = MAX<short>(0, h);
			}
		}
		hzb = MAX(hzb, sclipBottom);
	}
	if (fake3D & FAKE3D_CLIPTOP)
	{
		if (!spr->bIsVoxel)
		{
			double hz = sclipTop;
			if (spr->fakeceiling != NULL)
			{
				double ceilingZ = spr->fakeceiling->bottom.plane->Zat0();
				if (ViewPos.Z < ceilingZ && ceilingZ == sclipTop)
				{
					hz = spr->fakeceiling->top.plane->Zat0();
				}
			}
			int h = xs_RoundToInt(CenterY - (hz - ViewPos.Z) * scale);
			if (h > topclip)
			{
				topclip = short(MIN(h, viewheight));
			}
		}
		hzt = MIN(hzt, sclipTop);
	}

#if 0
	// [RH] Sprites that were split by a drawseg should also be clipped
	// by the sector's floor and ceiling. (Not sure how/if to handle this
	// with fake floors, since those already do clipping.)
	if (spr->bSplitSprite &&
		(spr->heightsec == NULL || (spr->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC)))
	{
		fixed_t h = spr->sector->floorplane.ZatPoint (spr->gx, spr->gy);
		h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS;
		if (h < botclip)
		{
			botclip = MAX<short> (0, h);
		}
		h = spr->sector->ceilingplane.ZatPoint (spr->gx, spr->gy);
		h = (centeryfrac - FixedMul (h-viewz, scale)) >> FRACBITS;
		if (h > topclip)
		{
			topclip = short(MIN(h, viewheight));
		}
	}
#endif

	if (topclip >= botclip)
	{
		spr->Style.BaseColormap = colormap;
		spr->Style.ColormapNum = colormapnum;
		return;
	}

	i = x2 - x1;
	clip1 = clipbot + x1;
	clip2 = cliptop + x1;
	do
	{
		*clip1++ = botclip;
		*clip2++ = topclip;
	} while (--i);

	// Scan drawsegs from end to start for obscuring segs.
	// The first drawseg that is closer than the sprite is the clip seg.

	// Modified by Lee Killough:
	// (pointer check was originally nonportable
	// and buggy, by going past LEFT end of array):

	//		for (ds=ds_p-1 ; ds >= drawsegs ; ds--)    old buggy code

	for (ds = ds_p; ds-- > firstdrawseg; )  // new -- killough
	{
		// [ZZ] portal handling here
		//if (ds->CurrentPortalUniq != spr->CurrentPortalUniq)
		//	continue;
		// [ZZ] WARNING: uncommenting the two above lines, totally breaks sprite clipping

		// kg3D - no clipping on fake segs
		if (ds->fake) continue;
		// determine if the drawseg obscures the sprite
		if (ds->x1 >= x2 || ds->x2 <= x1 ||
			(!(ds->silhouette & SIL_BOTH) && ds->maskedtexturecol == -1 &&
			 !ds->bFogBoundary) )
		{
			// does not cover sprite
			continue;
		}

		r1 = MAX<int> (ds->x1, x1);
		r2 = MIN<int> (ds->x2, x2);

		float neardepth, fardepth;
		if (!spr->bWallSprite)
		{
			if (ds->sz1 < ds->sz2)
			{
				neardepth = ds->sz1, fardepth = ds->sz2;
			}
			else
			{
				neardepth = ds->sz2, fardepth = ds->sz1;
			}
		}
		// Check if sprite is in front of draw seg:
		if ((!spr->bWallSprite && neardepth > spr->depth) || ((spr->bWallSprite || fardepth > spr->depth) &&
			(spr->gpos.Y - ds->curline->v1->fY()) * (ds->curline->v2->fX() - ds->curline->v1->fX()) -
			(spr->gpos.X - ds->curline->v1->fX()) * (ds->curline->v2->fY() - ds->curline->v1->fY()) <= 0))
		{
			// seg is behind sprite, so draw the mid texture if it has one
			if (ds->CurrentPortalUniq == CurrentPortalUniq && // [ZZ] instead, portal uniq check is made here
				(ds->maskedtexturecol != -1 || ds->bFogBoundary))
				R_RenderMaskedSegRange (ds, r1, r2);
			continue;
		}

		// clip this piece of the sprite
		// killough 3/27/98: optimized and made much shorter
		// [RH] Optimized further (at least for VC++;
		// other compilers should be at least as good as before)

		if (ds->silhouette & SIL_BOTTOM) //bottom sil
		{
			clip1 = clipbot + r1;
			clip2 = openings + ds->sprbottomclip + r1 - ds->x1;
			i = r2 - r1;
			do
			{
				if (*clip1 > *clip2)
					*clip1 = *clip2;
				clip1++;
				clip2++;
			} while (--i);
		}

		if (ds->silhouette & SIL_TOP)   // top sil
		{
			clip1 = cliptop + r1;
			clip2 = openings + ds->sprtopclip + r1 - ds->x1;
			i = r2 - r1;
			do
			{
				if (*clip1 < *clip2)
					*clip1 = *clip2;
				clip1++;
				clip2++;
			} while (--i);
		}
	}

	// all clipping has been performed, so draw the sprite

	if (!spr->bIsVoxel)
	{
		mfloorclip = clipbot;
		mceilingclip = cliptop;
		if (!spr->bWallSprite)
		{
			R_DrawVisSprite(spr);
		}
		else
		{
			R_DrawWallSprite(spr);
		}
	}
	else
	{
		// If it is completely clipped away, don't bother drawing it.
		if (cliptop[x2] >= clipbot[x2])
		{
			for (i = x1; i < x2; ++i)
			{
				if (cliptop[i] < clipbot[i])
				{
					break;
				}
			}
			if (i == x2)
			{
				spr->Style.BaseColormap = colormap;
				spr->Style.ColormapNum = colormapnum;
				return;
			}
		}
		// Add everything outside the left and right edges to the clipping array
		// for R_DrawVisVoxel().
		if (x1 > 0)
		{
			fillshort(cliptop, x1, viewheight);
		}
		if (x2 < viewwidth - 1)
		{
			fillshort(cliptop + x2, viewwidth - x2, viewheight);
		}
		int minvoxely = spr->gzt <= hzt ? 0 : xs_RoundToInt((spr->gzt - hzt) / spr->yscale);
		int maxvoxely = spr->gzb > hzb ? INT_MAX : xs_RoundToInt((spr->gzt - hzb) / spr->yscale);
		R_DrawVisVoxel(spr, minvoxely, maxvoxely, cliptop, clipbot);
	}
	spr->Style.BaseColormap = colormap;
	spr->Style.ColormapNum = colormapnum;
}

// kg3D:
// R_DrawMasked contains sorting
// original renamed to R_DrawMaskedSingle

void R_DrawMaskedSingle (bool renew)
{
	drawseg_t *ds;
	int i;

#if 0
	R_SplitVisSprites ();
#endif

	for (i = vsprcount; i > 0; i--)
	{
		if (spritesorter[i-1]->CurrentPortalUniq != CurrentPortalUniq)
			continue; // probably another time
		R_DrawSprite (spritesorter[i-1]);
	}

	// render any remaining masked mid textures

	// Modified by Lee Killough:
	// (pointer check was originally nonportable
	// and buggy, by going past LEFT end of array):

	//		for (ds=ds_p-1 ; ds >= drawsegs ; ds--)    old buggy code

	if (renew)
	{
		fake3D |= FAKE3D_REFRESHCLIP;
	}
	for (ds = ds_p; ds-- > firstdrawseg; )	// new -- killough
	{
		// [ZZ] the same as above
		if (ds->CurrentPortalUniq != CurrentPortalUniq)
			continue;
		// kg3D - no fake segs
		if (ds->fake) continue;
		if (ds->maskedtexturecol != -1 || ds->bFogBoundary)
		{
			R_RenderMaskedSegRange (ds, ds->x1, ds->x2);
		}
	}
}

void R_DrawHeightPlanes(double height); // kg3D - fake planes

void R_DrawMasked (void)
{
	R_CollectPortals();
	R_SortVisSprites (DrewAVoxel ? sv_compare2d : sv_compare, firstvissprite - vissprites);

	if (height_top == NULL)
	{ // kg3D - no visible 3D floors, normal rendering
		R_DrawMaskedSingle(false);
	}
	else
	{ // kg3D - correct sorting
		HeightLevel *hl;

		// ceilings
		for (hl = height_cur; hl != NULL && hl->height >= ViewPos.Z; hl = hl->prev)
		{
			if (hl->next)
			{
				fake3D = FAKE3D_CLIPBOTTOM | FAKE3D_CLIPTOP;
				sclipTop = hl->next->height;
			}
			else
			{
				fake3D = FAKE3D_CLIPBOTTOM;
			}
			sclipBottom = hl->height;
			R_DrawMaskedSingle(true);
			R_DrawHeightPlanes(hl->height);
		}

		// floors
		fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP;
		sclipTop = height_top->height;
		R_DrawMaskedSingle(true);
		hl = height_top;
		for (hl = height_top; hl != NULL && hl->height < ViewPos.Z; hl = hl->next)
		{
			R_DrawHeightPlanes(hl->height);
			if (hl->next)
			{
				fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPTOP | FAKE3D_CLIPBOTTOM;
				sclipTop = hl->next->height;
			}
			else
			{
				fake3D = FAKE3D_DOWN2UP | FAKE3D_CLIPBOTTOM;
			}
			sclipBottom = hl->height;
			R_DrawMaskedSingle(true);
		}
		R_3D_DeleteHeights();
		fake3D = 0;
	}
	R_DrawPlayerSprites ();
}


void R_ProjectParticle (particle_t *particle, const sector_t *sector, int shade, int fakeside)
{
	double 				tr_x, tr_y;
	double 				tx, ty;
	double	 			tz, tiz;
	double	 			xscale, yscale;
	int 				x1, x2, y1, y2;
	vissprite_t*		vis;
	sector_t*			heightsec = NULL;
	FSWColormap*			map;

	// [ZZ] Particle not visible through the portal plane
	if (CurrentPortal && !!P_PointOnLineSide(particle->Pos, CurrentPortal->dst))
		return;

	// transform the origin point
	tr_x = particle->Pos.X - ViewPos.X;
	tr_y = particle->Pos.Y - ViewPos.Y;

	tz = tr_x * ViewTanCos + tr_y * ViewTanSin;

	// particle is behind view plane?
	if (tz < MINZ)
		return;

	tx = tr_x * ViewSin - tr_y * ViewCos;

	// Flip for mirrors
	if (MirrorFlags & RF_XFLIP)
	{
		tx = viewwidth - tx - 1;
	}

	// too far off the side?
	if (tz <= fabs(tx))
		return;

	tiz = 1 / tz;
	xscale = centerx * tiz;

	// calculate edges of the shape
	double psize = particle->size / 8.0;

	x1 = MAX<int>(WindowLeft, centerx + xs_RoundToInt((tx - psize) * xscale));
	x2 = MIN<int>(WindowRight, centerx + xs_RoundToInt((tx + psize) * xscale));

	if (x1 >= x2)
		return;

	yscale = YaspectMul * xscale;
	ty = particle->Pos.Z - ViewPos.Z;
	y1 = xs_RoundToInt(CenterY - (ty + psize) * yscale);
	y2 = xs_RoundToInt(CenterY - (ty - psize) * yscale);

	// Clip the particle now. Because it's a point and projected as its subsector is
	// entered, we don't need to clip it to drawsegs like a normal sprite.

	// Clip particles behind walls.
	if (y1 <  ceilingclip[x1])		y1 = ceilingclip[x1];
	if (y1 <  ceilingclip[x2-1])	y1 = ceilingclip[x2-1];
	if (y2 >= floorclip[x1])		y2 = floorclip[x1] - 1;
	if (y2 >= floorclip[x2-1])		y2 = floorclip[x2-1] - 1;

	if (y1 > y2)
		return;

	// Clip particles above the ceiling or below the floor.
	heightsec = sector->GetHeightSec();

	const secplane_t *topplane;
	const secplane_t *botplane;
	FTextureID toppic;
	FTextureID botpic;

	if (heightsec)	// only clip things which are in special sectors
	{
		if (fakeside == FAKED_AboveCeiling)
		{
			topplane = &sector->ceilingplane;
			botplane = &heightsec->ceilingplane;
			toppic = sector->GetTexture(sector_t::ceiling);
			botpic = heightsec->GetTexture(sector_t::ceiling);
			map = heightsec->ColorMap;
		}
		else if (fakeside == FAKED_BelowFloor)
		{
			topplane = &heightsec->floorplane;
			botplane = &sector->floorplane;
			toppic = heightsec->GetTexture(sector_t::floor);
			botpic = sector->GetTexture(sector_t::floor);
			map = heightsec->ColorMap;
		}
		else
		{
			topplane = &heightsec->ceilingplane;
			botplane = &heightsec->floorplane;
			toppic = heightsec->GetTexture(sector_t::ceiling);
			botpic = heightsec->GetTexture(sector_t::floor);
			map = sector->ColorMap;
		}
	}
	else
	{
		topplane = &sector->ceilingplane;
		botplane = &sector->floorplane;
		toppic = sector->GetTexture(sector_t::ceiling);
		botpic = sector->GetTexture(sector_t::floor);
		map = sector->ColorMap;
	}

	if (botpic != skyflatnum && particle->Pos.Z < botplane->ZatPoint (particle->Pos))
		return;
	if (toppic != skyflatnum && particle->Pos.Z >= topplane->ZatPoint (particle->Pos))
		return;

	// store information in a vissprite
	vis = R_NewVisSprite ();
	vis->CurrentPortalUniq = CurrentPortalUniq;
	vis->heightsec = heightsec;
	vis->xscale = FLOAT2FIXED(xscale);
	vis->yscale = (float)xscale;
//	vis->yscale *= InvZtoScale;
	vis->depth = (float)tz;
	vis->idepth = float(1 / tz);
	vis->gpos = { (float)particle->Pos.X, (float)particle->Pos.Y, (float)particle->Pos.Z };
	vis->y1 = y1;
	vis->y2 = y2;
	vis->x1 = x1;
	vis->x2 = x2;
	vis->Translation = 0;
	vis->startfrac = 255 & (particle->color >>24);
	vis->pic = NULL;
	vis->bIsVoxel = false;
	vis->renderflags = particle->trans;
	vis->FakeFlatStat = fakeside;
	vis->floorclip = 0;
	vis->Style.ColormapNum = 0;

	if (fixedlightlev >= 0)
	{
		vis->Style.BaseColormap = map;
		vis->Style.ColormapNum = fixedlightlev >> COLORMAPSHIFT;
	}
	else if (fixedcolormap)
	{
		vis->Style.BaseColormap = fixedcolormap;
		vis->Style.ColormapNum = 0;
	}
	else if (particle->bright)
	{
		vis->Style.BaseColormap = (r_fullbrightignoresectorcolor) ? &FullNormalLight : map;
		vis->Style.ColormapNum = 0;
	}
	else
	{
		// Particles are slightly more visible than regular sprites.
		vis->Style.ColormapNum = GETPALOOKUP(tiz * r_SpriteVisibility * 0.5, shade);
		vis->Style.BaseColormap = map;
	}
}

static void R_DrawMaskedSegsBehindParticle (const vissprite_t *vis)
{
	const int x1 = vis->x1;
	const int x2 = vis->x2;

	// Draw any masked textures behind this particle so that when the
	// particle is drawn, it will be in front of them.
	for (unsigned int p = InterestingDrawsegs.Size(); p-- > FirstInterestingDrawseg; )
	{
		drawseg_t *ds = &drawsegs[InterestingDrawsegs[p]];
		// kg3D - no fake segs
		if(ds->fake) continue;
		if (ds->x1 >= x2 || ds->x2 <= x1)
		{
			continue;
		}
		if ((ds->siz2 - ds->siz1) * ((x2 + x1)/2 - ds->sx1) / (ds->sx2 - ds->sx1) + ds->siz1 < vis->idepth)
		{
			// [ZZ] only draw stuff that's inside the same portal as the particle, other portals will care for themselves
			if (ds->CurrentPortalUniq == vis->CurrentPortalUniq)
				R_RenderMaskedSegRange (ds, MAX<int>(ds->x1, x1), MIN<int>(ds->x2, x2));
		}
	}
}

void R_DrawParticle_C (vissprite_t *vis)
{
	DWORD *bg2rgb;
	int spacing;
	BYTE *dest;
	DWORD fg;
	BYTE color = vis->Style.BaseColormap->Maps[(vis->Style.ColormapNum << COLORMAPSHIFT) + vis->startfrac];
	int yl = vis->y1;
	int ycount = vis->y2 - yl + 1;
	int x1 = vis->x1;
	int countbase = vis->x2 - x1;

	R_DrawMaskedSegsBehindParticle (vis);

	DrawerCommandQueue::WaitForWorkers();

	// vis->renderflags holds translucency level (0-255)
	{
		fixed_t fglevel, bglevel;
		DWORD *fg2rgb;

		fglevel = ((vis->renderflags + 1) << 8) & ~0x3ff;
		bglevel = FRACUNIT-fglevel;
		fg2rgb = Col2RGB8[fglevel>>10];
		bg2rgb = Col2RGB8[bglevel>>10];
		fg = fg2rgb[color];
	}

	/*

	spacing = RenderTarget->GetPitch() - countbase;
	dest = ylookup[yl] + x1 + dc_destorg;

	do
	{
		int count = countbase;
		do
		{
			DWORD bg = bg2rgb[*dest];
			bg = (fg+bg) | 0x1f07c1f;
			*dest++ = RGB32k.All[bg & (bg>>15)];
		} while (--count);
		dest += spacing;
	} while (--ycount);*/

	// original was row-wise
	// width = countbase
	// height = ycount

	spacing = RenderTarget->GetPitch();

	for (int x = x1; x < (x1+countbase); x++)
	{
		dc_x = x;
		if (R_ClipSpriteColumnWithPortals(vis))
			continue;
		dest = ylookup[yl] + x + dc_destorg;
		for (int y = 0; y < ycount; y++)
		{
			DWORD bg = bg2rgb[*dest];
			bg = (fg+bg) | 0x1f07c1f;
			*dest = RGB32k.All[bg & (bg>>15)];
			dest += spacing;
		}
	}
}

void R_DrawParticle_rgba(vissprite_t *vis)
{
	int spacing;
	uint32_t *dest;
	BYTE color = vis->Style.BaseColormap->Maps[vis->startfrac];
	int yl = vis->y1;
	int ycount = vis->y2 - yl + 1;
	int x1 = vis->x1;
	int countbase = vis->x2 - x1;

	R_DrawMaskedSegsBehindParticle(vis);
	
	DrawerCommandQueue::WaitForWorkers();

	uint32_t fg = LightBgra::shade_pal_index_simple(color, LightBgra::calc_light_multiplier(LIGHTSCALE(0, vis->Style.ColormapNum << FRACBITS)));
	uint32_t fg_red = (fg >> 16) & 0xff;
	uint32_t fg_green = (fg >> 8) & 0xff;
	uint32_t fg_blue = fg & 0xff;

	// vis->renderflags holds translucency level (0-255)
	fixed_t fglevel = ((vis->renderflags + 1) << 8) & ~0x3ff;
	uint32_t alpha = fglevel * 256 / FRACUNIT;
	uint32_t inv_alpha = 256 - alpha;

	fg_red *= alpha;
	fg_green *= alpha;
	fg_blue *= alpha;

	spacing = RenderTarget->GetPitch();

	for (int x = x1; x < (x1 + countbase); x++)
	{
		dc_x = x;
		if (R_ClipSpriteColumnWithPortals(vis))
			continue;
		dest = ylookup[yl] + x + (uint32_t*)dc_destorg;
		for (int y = 0; y < ycount; y++)
		{
			uint32_t bg_red = (*dest >> 16) & 0xff;
			uint32_t bg_green = (*dest >> 8) & 0xff;
			uint32_t bg_blue = (*dest) & 0xff;

			uint32_t red = (fg_red + bg_red * inv_alpha) / 256;
			uint32_t green = (fg_green + bg_green * inv_alpha) / 256;
			uint32_t blue = (fg_blue + bg_blue * inv_alpha) / 256;

			*dest = 0xff000000 | (red << 16) | (green << 8) | blue;
			dest += spacing;
		}
	}
}

extern double BaseYaspectMul;;

inline int sgn(int v)
{
	return v < 0 ? -1 : v > 0 ? 1 : 0;
}

void R_DrawVoxel(const FVector3 &globalpos, FAngle viewangle,
	const FVector3 &dasprpos, DAngle dasprang,
	fixed_t daxscale, fixed_t dayscale, FVoxel *voxobj,
	FSWColormap *colormap, int colormapnum, short *daumost, short *dadmost, int minslabz, int maxslabz, int flags)
{
	int i, j, k, x, y, syoff, ggxstart, ggystart, nxoff;
	fixed_t cosang, sinang, sprcosang, sprsinang;
	int backx, backy, gxinc, gyinc;
	int daxscalerecip, dayscalerecip, cnt, gxstart, gystart, dazscale;
	int lx, rx, nx, ny, x1=0, y1=0, x2=0, y2=0, yinc=0;
	int yoff, xs=0, ys=0, xe, ye, xi=0, yi=0, cbackx, cbacky, dagxinc, dagyinc;
	kvxslab_t *voxptr, *voxend;
	FVoxelMipLevel *mip;
	int z1a[64], z2a[64], yplc[64];

	const int nytooclose = centerxwide * 2100, nytoofar = 32768*32768 - 1048576;
	const int xdimenscale = FLOAT2FIXED(centerxwide * YaspectMul / 160);
	const double centerxwide_f = centerxwide;
	const double centerxwidebig_f = centerxwide_f * 65536*65536*8;

	// Convert to Build's coordinate system.
	fixed_t globalposx = xs_Fix<4>::ToFix(globalpos.X);
	fixed_t globalposy = xs_Fix<4>::ToFix(-globalpos.Y);
	fixed_t globalposz = xs_Fix<8>::ToFix(-globalpos.Z);

	fixed_t dasprx = xs_Fix<4>::ToFix(dasprpos.X);
	fixed_t daspry = xs_Fix<4>::ToFix(-dasprpos.Y);
	fixed_t dasprz = xs_Fix<8>::ToFix(-dasprpos.Z);

	// Shift the scales from 16 bits of fractional precision to 6.
	// Also do some magic voodoo scaling to make them the right size.
	daxscale = daxscale / (0xC000 >> 6);
	dayscale = dayscale / (0xC000 >> 6);
	if (daxscale <= 0 || dayscale <= 0)
	{
		// won't be visible.
		return;
	}

	angle_t viewang = viewangle.BAMs();
	cosang = FLOAT2FIXED(viewangle.Cos()) >> 2;
	sinang = FLOAT2FIXED(-viewangle.Sin()) >> 2;
	sprcosang = FLOAT2FIXED(dasprang.Cos()) >> 2;
	sprsinang = FLOAT2FIXED(-dasprang.Sin()) >> 2;

	R_SetupDrawSlab(colormap, 0.0f, colormapnum << FRACBITS);

	int pixelsize = r_swtruecolor ? 4 : 1;

	// Select mip level
	i = abs(DMulScale6(dasprx - globalposx, cosang, daspry - globalposy, sinang));
	i = DivScale6(i, MIN(daxscale, dayscale));
	j = xs_Fix<13>::ToFix(FocalLengthX);
	for (k = 0; i >= j && k < voxobj->NumMips; ++k)
	{
		i >>= 1;
	}
	if (k >= voxobj->NumMips) k = voxobj->NumMips - 1;

	mip = &voxobj->Mips[k];		if (mip->SlabData == NULL) return;

	minslabz >>= k;
	maxslabz >>= k;

	daxscale <<= (k+8); dayscale <<= (k+8);
	dazscale = FixedDiv(dayscale, FLOAT2FIXED(BaseYaspectMul));
	daxscale = fixed_t(daxscale / YaspectMul);
	daxscale = Scale(daxscale, xdimenscale, centerxwide << 9);
	dayscale = Scale(dayscale, FixedMul(xdimenscale, viewingrangerecip), centerxwide << 9);

	daxscalerecip = (1<<30) / daxscale;
	dayscalerecip = (1<<30) / dayscale;

	fixed_t piv_x = fixed_t(mip->Pivot.X*256.);
	fixed_t piv_y = fixed_t(mip->Pivot.Y*256.);
	fixed_t piv_z = fixed_t(mip->Pivot.Z*256.);

	x = FixedMul(globalposx - dasprx, daxscalerecip);
	y = FixedMul(globalposy - daspry, daxscalerecip);
	backx = (DMulScale10(x, sprcosang, y,  sprsinang) + piv_x) >> 8;
	backy = (DMulScale10(y, sprcosang, x, -sprsinang) + piv_y) >> 8;
	cbackx = clamp(backx, 0, mip->SizeX - 1);
	cbacky = clamp(backy, 0, mip->SizeY - 1);

	sprcosang = MulScale14(daxscale, sprcosang);
	sprsinang = MulScale14(daxscale, sprsinang);

	x = (dasprx - globalposx) - DMulScale18(piv_x, sprcosang, piv_y, -sprsinang);
	y = (daspry - globalposy) - DMulScale18(piv_y, sprcosang, piv_x,  sprsinang);

	cosang = FixedMul(cosang, dayscalerecip);
	sinang = FixedMul(sinang, dayscalerecip);

	gxstart = y*cosang - x*sinang;
	gystart = x*cosang + y*sinang;
	gxinc = DMulScale10(sprsinang, cosang, sprcosang, -sinang);
	gyinc = DMulScale10(sprcosang, cosang, sprsinang,  sinang);
	if ((abs(globalposz - dasprz) >> 10) >= abs(dazscale)) return;

	x = 0; y = 0; j = MAX(mip->SizeX, mip->SizeY);
	fixed_t *ggxinc = (fixed_t *)alloca((j + 1) * sizeof(fixed_t) * 2);
	fixed_t *ggyinc = ggxinc + (j + 1);
	for (i = 0; i <= j; i++)
	{
		ggxinc[i] = x; x += gxinc;
		ggyinc[i] = y; y += gyinc;
	}

	syoff = DivScale21(globalposz - dasprz, FixedMul(dazscale, 0xE800)) + (piv_z << 7);
	yoff = (abs(gxinc) + abs(gyinc)) >> 1;

	for (cnt = 0; cnt < 8; cnt++)
	{
		switch (cnt)
		{
			case 0: xs = 0;				ys = 0;				xi =  1; yi =  1; break;
			case 1: xs = mip->SizeX-1;	ys = 0;				xi = -1; yi =  1; break;
			case 2: xs = 0;				ys = mip->SizeY-1;	xi =  1; yi = -1; break;
			case 3: xs = mip->SizeX-1;	ys = mip->SizeY-1;	xi = -1; yi = -1; break;
			case 4: xs = 0;				ys = cbacky;		xi =  1; yi =  2; break;
			case 5: xs = mip->SizeX-1;	ys = cbacky;		xi = -1; yi =  2; break;
			case 6: xs = cbackx;		ys = 0;				xi =  2; yi =  1; break;
			case 7: xs = cbackx;		ys = mip->SizeY-1;	xi =  2; yi = -1; break;
		}
		xe = cbackx; ye = cbacky;
		if (cnt < 4)
		{
			if ((xi < 0) && (xe >= xs)) continue;
			if ((xi > 0) && (xe <= xs)) continue;
			if ((yi < 0) && (ye >= ys)) continue;
			if ((yi > 0) && (ye <= ys)) continue;
		}
		else
		{
			if ((xi < 0) && (xe > xs)) continue;
			if ((xi > 0) && (xe < xs)) continue;
			if ((yi < 0) && (ye > ys)) continue;
			if ((yi > 0) && (ye < ys)) continue;
			xe += xi; ye += yi;
		}

		i = sgn(ys - backy) + sgn(xs - backx) * 3 + 4;
		switch(i)
		{
			case 6: case 7: x1 = 0;				y1 = 0;				break;
			case 8: case 5: x1 = gxinc;			y1 = gyinc;			break;
			case 0: case 3: x1 = gyinc;			y1 = -gxinc;		break;
			case 2: case 1: x1 = gxinc+gyinc;	y1 = gyinc-gxinc;	break;
		}
		switch(i)
		{
			case 2: case 5: x2 = 0;				y2 = 0;				break;
			case 0: case 1: x2 = gxinc;			y2 = gyinc;			break;
			case 8: case 7: x2 = gyinc;			y2 = -gxinc;		break;
			case 6: case 3: x2 = gxinc+gyinc;	y2 = gyinc-gxinc;	break;
		}
		BYTE oand = (1 << int(xs<backx)) + (1 << (int(ys<backy)+2));
		BYTE oand16 = oand + 16;
		BYTE oand32 = oand + 32;

		if (yi > 0) { dagxinc =  gxinc; dagyinc =  FixedMul(gyinc, viewingrangerecip); }
			   else { dagxinc = -gxinc; dagyinc = -FixedMul(gyinc, viewingrangerecip); }

			/* Fix for non 90 degree viewing ranges */
		nxoff = FixedMul(x2 - x1, viewingrangerecip);
		x1 = FixedMul(x1, viewingrangerecip);

		ggxstart = gxstart + ggyinc[ys];
		ggystart = gystart - ggxinc[ys];

		for (x = xs; x != xe; x += xi)
		{
			BYTE *slabxoffs = &mip->SlabData[mip->OffsetX[x]];
			short *xyoffs = &mip->OffsetXY[x * (mip->SizeY + 1)];

			nx = FixedMul(ggxstart + ggxinc[x], viewingrangerecip) + x1;
			ny = ggystart + ggyinc[x];
			for (y = ys; y != ye; y += yi, nx += dagyinc, ny -= dagxinc)
			{
				if ((ny <= nytooclose) || (ny >= nytoofar)) continue;
				voxptr = (kvxslab_t *)(slabxoffs + xyoffs[y]);
				voxend = (kvxslab_t *)(slabxoffs + xyoffs[y+1]);
				if (voxptr >= voxend) continue;

				lx = xs_RoundToInt(nx * centerxwide_f / (ny + y1)) + centerx;
				if (lx < 0) lx = 0;
				rx = xs_RoundToInt((nx + nxoff) * centerxwide_f / (ny + y2)) + centerx;
				if (rx > viewwidth) rx = viewwidth;
				if (rx <= lx) continue;

				if (flags & DVF_MIRRORED)
				{
					int t = viewwidth - lx;
					lx = viewwidth - rx;
					rx = t;
				}

				fixed_t l1 = xs_RoundToInt(centerxwidebig_f / (ny - yoff));
				fixed_t l2 = xs_RoundToInt(centerxwidebig_f / (ny + yoff));
				for (; voxptr < voxend; voxptr = (kvxslab_t *)((BYTE *)voxptr + voxptr->zleng + 3))
				{
					const BYTE *col = voxptr->col;
					int zleng = voxptr->zleng;
					int ztop = voxptr->ztop;
					fixed_t z1, z2;

					if (ztop < minslabz)
					{
						int diff = minslabz - ztop;
						ztop = minslabz;
						col += diff;
						zleng -= diff;
					}
					if (ztop + zleng > maxslabz)
					{
						int diff = ztop + zleng - maxslabz;
						zleng -= diff;
					}
					if (zleng <= 0) continue;

					j = (ztop << 15) - syoff;
					if (j < 0)
					{
						k = j + (zleng << 15);
						if (k < 0)
						{
							if ((voxptr->backfacecull & oand32) == 0) continue;
							z2 = MulScale32(l2, k) + centery;					/* Below slab */
						}
						else
						{
							if ((voxptr->backfacecull & oand) == 0) continue;	/* Middle of slab */
							z2 = MulScale32(l1, k) + centery;
						}
						z1 = MulScale32(l1, j) + centery;
					}
					else
					{
						if ((voxptr->backfacecull & oand16) == 0) continue;
						z1 = MulScale32(l2, j) + centery;						/* Above slab */
						z2 = MulScale32(l1, j + (zleng << 15)) + centery;
					}

					if (z2 <= z1) continue;

					if (zleng == 1)
					{
						yinc = 0;
					}
					else
					{
						if (z2-z1 >= 1024) yinc = FixedDiv(zleng, z2 - z1);
						else yinc = (((1 << 24) - 1) / (z2 - z1)) * zleng >> 8;
					}
					// [RH] Clip each column separately, not just by the first one.
					for (int stripwidth = MIN<int>(countof(z1a), rx - lx), lxt = lx;
						lxt < rx;
						(lxt += countof(z1a)), stripwidth = MIN<int>(countof(z1a), rx - lxt))
					{
						// Calculate top and bottom pixels locations
						for (int xxx = 0; xxx < stripwidth; ++xxx)
						{
							if (zleng == 1)
							{
								yplc[xxx] = 0;
								z1a[xxx] = MAX<int>(z1, daumost[lxt + xxx]);
							}
							else
							{
								if (z1 < daumost[lxt + xxx])
								{
									yplc[xxx] = yinc * (daumost[lxt + xxx] - z1);
									z1a[xxx] = daumost[lxt + xxx];
								}
								else
								{
									yplc[xxx] = 0;
									z1a[xxx] = z1;
								}
							}
							z2a[xxx] = MIN<int>(z2, dadmost[lxt + xxx]);
						}
						// Find top and bottom pixels that match and draw them as one strip
						for (int xxl = 0, xxr; xxl < stripwidth; )
						{
							if (z1a[xxl] >= z2a[xxl])
							{ // No column here
								xxl++;
								continue;
							}
							int z1 = z1a[xxl];
							int z2 = z2a[xxl];
							// How many columns share the same extents?
							for (xxr = xxl + 1; xxr < stripwidth; ++xxr)
							{
								if (z1a[xxr] != z1 || z2a[xxr] != z2)
									break;
							}

							if (!(flags & DVF_OFFSCREEN))
							{
								// Draw directly to the screen.
								R_DrawSlab(xxr - xxl, yplc[xxl], z2 - z1, yinc, col, (ylookup[z1] + lxt + xxl) * pixelsize + dc_destorg);
							}
							else
							{
								// Record the area covered and possibly draw to an offscreen buffer.
								dc_yl = z1;
								dc_yh = z2 - 1;
								dc_count = z2 - z1;
								dc_iscale = yinc;
								for (int x = xxl; x < xxr; ++x)
								{
									OffscreenCoverageBuffer->InsertSpan(lxt + x, z1, z2);
									if (!(flags & DVF_SPANSONLY))
									{
										dc_x = lxt + x;
										rt_initcols(OffscreenColorBuffer + (dc_x & ~3) * OffscreenBufferHeight);
										dc_source = col;
										dc_source2 = nullptr;
										dc_texturefrac = yplc[xxl];
										hcolfunc_pre();
									}
								}
							}
							xxl = xxr;
						}
					}
				}
			}
		}
	}
}

//==========================================================================
//
// FCoverageBuffer Constructor
//
//==========================================================================

FCoverageBuffer::FCoverageBuffer(int lists)
	: Spans(NULL), FreeSpans(NULL)
{
	NumLists = lists;
	Spans = new Span *[lists];
	memset(Spans, 0, sizeof(Span*)*lists);
}

//==========================================================================
//
// FCoverageBuffer Destructor
//
//==========================================================================

FCoverageBuffer::~FCoverageBuffer()
{
	if (Spans != NULL)
	{
		delete[] Spans;
	}
}

//==========================================================================
//
// FCoverageBuffer :: Clear
//
//==========================================================================

void FCoverageBuffer::Clear()
{
	SpanArena.FreeAll();
	memset(Spans, 0, sizeof(Span*)*NumLists);
	FreeSpans = NULL;
}

//==========================================================================
//
// FCoverageBuffer :: InsertSpan
//
// start is inclusive.
// stop is exclusive.
//
//==========================================================================

void FCoverageBuffer::InsertSpan(int listnum, int start, int stop)
{
	assert(unsigned(listnum) < NumLists);
	assert(start < stop);

	Span **span_p = &Spans[listnum];
	Span *span;

	if (*span_p == NULL || (*span_p)->Start > stop)
	{ // This list is empty or the first entry is after this one, so we can just insert the span.
		goto addspan;
	}

	// Insert the new span in order, merging with existing ones.
	while (*span_p != NULL)
	{
		if ((*span_p)->Stop < start)							// =====		(existing span)
		{ // Span ends before this one starts.					//		  ++++	(new span)
			span_p = &(*span_p)->NextSpan;
			continue;
		}

		// Does the new span overlap or abut the existing one?
		if ((*span_p)->Start <= start)
		{
			if ((*span_p)->Stop >= stop)						// =============
			{ // The existing span completely covers this one.	//     +++++
				return;
			}
extend:		// Extend the existing span with the new one.		// ======
			span = *span_p;										//     +++++++
			span->Stop = stop;									// (or)  +++++

			// Free up any spans we just covered up.
			span_p = &(*span_p)->NextSpan;
			while (*span_p != NULL && (*span_p)->Start <= stop && (*span_p)->Stop <= stop)
			{
				Span *span = *span_p;							// ======  ======
				*span_p = span->NextSpan;						//     +++++++++++++
				span->NextSpan = FreeSpans;
				FreeSpans = span;
			}
			if (*span_p != NULL && (*span_p)->Start <= stop)	// =======         ========
			{ // Our new span connects two existing spans.		//     ++++++++++++++
			  // They should all be collapsed into a single span.
				span->Stop = (*span_p)->Stop;
				span = *span_p;
				*span_p = span->NextSpan;
				span->NextSpan = FreeSpans;
				FreeSpans = span;
			}
			goto check;
		}
		else if ((*span_p)->Start <= stop)						//        =====
		{ // The new span extends the existing span from		//    ++++
		  // the beginning.										// (or) ++++
			(*span_p)->Start = start;
			if ((*span_p)->Stop < stop)
			{ // The new span also extends the existing span	//     ======
			  // at the bottom									// ++++++++++++++
				goto extend;
			}
			goto check;
		}
		else													//         ======
		{ // No overlap, so insert a new span.					// +++++
			goto addspan;
		}
	}
	// Append a new span to the end of the list.
addspan:
	span = AllocSpan();
	span->NextSpan = *span_p;
	span->Start = start;
	span->Stop = stop;
	*span_p = span;
check:
#ifdef _DEBUG
	// Validate the span list: Spans must be in order, and there must be
	// at least one pixel between spans.
	for (span = Spans[listnum]; span != NULL; span = span->NextSpan)
	{
		assert(span->Start < span->Stop);
		if (span->NextSpan != NULL)
		{
			assert(span->Stop < span->NextSpan->Start);
		}
	}
#endif
	;
}

//==========================================================================
//
// FCoverageBuffer :: AllocSpan
//
//==========================================================================

FCoverageBuffer::Span *FCoverageBuffer::AllocSpan()
{
	Span *span;

	if (FreeSpans != NULL)
	{
		span = FreeSpans;
		FreeSpans = span->NextSpan;
	}
	else
	{
		span = (Span *)SpanArena.Alloc(sizeof(Span));
	}
	return span;
}

//==========================================================================
//
// R_CheckOffscreenBuffer
//
// Allocates the offscreen coverage buffer and optionally the offscreen
// color buffer. If they already exist but are the wrong size, they will
// be reallocated.
//
//==========================================================================

void R_CheckOffscreenBuffer(int width, int height, bool spansonly)
{
	if (OffscreenCoverageBuffer == NULL)
	{
		assert(OffscreenColorBuffer == NULL && "The color buffer cannot exist without the coverage buffer");
		OffscreenCoverageBuffer = new FCoverageBuffer(width);
	}
	else if (OffscreenCoverageBuffer->NumLists != (unsigned)width)
	{
		delete OffscreenCoverageBuffer;
		OffscreenCoverageBuffer = new FCoverageBuffer(width);
		if (OffscreenColorBuffer != NULL)
		{
			delete[] OffscreenColorBuffer;
			OffscreenColorBuffer = NULL;
		}
	}
	else
	{
		OffscreenCoverageBuffer->Clear();
	}

	if (!spansonly)
	{
		if (OffscreenColorBuffer == NULL)
		{
			OffscreenColorBuffer = new BYTE[width * height * 4];
		}
		else if (OffscreenBufferWidth != width || OffscreenBufferHeight != height)
		{
			delete[] OffscreenColorBuffer;
			OffscreenColorBuffer = new BYTE[width * height * 4];
		}
	}
	OffscreenBufferWidth = width;
	OffscreenBufferHeight = height;
}

}
