// BLANKART
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2020 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  r_main.c
/// \brief Rendering main loop and setup functions,
///        utility functions (BSP, geometry, trigonometry).
///        See tables.c, too.

#include <algorithm>
#include <cmath>

#include "doomdef.h"
#include "g_game.h"
#include "g_input.h"
#include "r_local.h"
#include "r_splats.h" // faB(21jan): testing
#include "r_sky.h"
#include "hu_stuff.h"
#include "st_stuff.h"
#include "p_local.h"
#include "keys.h"
#include "i_video.h"
#include "m_menu.h"
#include "am_map.h"
#include "d_main.h"
#include "v_video.h"
#include "p_spec.h" // skyboxmo
#include "p_setup.h"
#include "z_zone.h"
#include "m_random.h" // quake camera shake
#include "r_portal.h"
#include "r_main.h"
#include "i_system.h" // I_GetPreciseTime
#include "doomstat.h" // MAXSPLITSCREENPLAYERS
#include "r_fps.h" // Frame interpolation/uncapped
#include "core/thread_pool.h"

#ifdef HWRENDER
#include "hardware/hw_main.h"
#endif

extern consvar_t cv_debugrender_visplanes;
extern consvar_t cv_debugrender_freezebsp;

// Fineangles in the SCREENWIDTH wide window.
#define FIELDOFVIEW 2048

// Double-precision Pi value
#define DOUBLE_PI 3.141592653589793

// increment every time a check is made
size_t validcount = 1;

INT32 centerx, centery;

fixed_t centerxfrac, centeryfrac;
fixed_t projection[MAXSPLITSCREENPLAYERS];
fixed_t projectiony[MAXSPLITSCREENPLAYERS]; // aspect ratio
fixed_t fovtan[MAXSPLITSCREENPLAYERS]; // field of view

// just for profiling purposes
size_t framecount;

size_t loopcount;

fixed_t viewx, viewy, viewz;
angle_t viewangle, aimingangle, viewroll;
UINT8 viewssnum;
fixed_t viewcos, viewsin;
boolean skyVisible;
boolean skyVisiblePerPlayer[MAXSPLITSCREENPLAYERS]; // saved values of skyVisible for each splitscreen player
sector_t *viewsector;
player_t *viewplayer;
mobj_t *r_viewmobj;

int r_splitscreen;

fixed_t renderdeltatics;
boolean renderisnewtic;

//
// precalculated math tables
//
angle_t clipangle[MAXSPLITSCREENPLAYERS];
angle_t doubleclipangle[MAXSPLITSCREENPLAYERS];

// The viewangletox[viewangle + FINEANGLES/4] lookup
// maps the visible view angles to screen X coordinates,
// flattening the arc to a flat projection plane.
// There will be many angles mapped to the same X.
INT32 viewangletox[MAXSPLITSCREENPLAYERS][FINEANGLES/2];

// The xtoviewangleangle[] table maps a screen pixel
// to the lowest viewangle that maps back to x ranges
// from clipangle to -clipangle.
angle_t xtoviewangle[MAXSPLITSCREENPLAYERS][MAXVIDWIDTH+1];

lighttable_t *scalelight[LIGHTLEVELS][MAXLIGHTSCALE];
lighttable_t *scalelightfixed[MAXLIGHTSCALE];
lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ];

// Hack to support extra boom colormaps.
extracolormap_t *extra_colormaps = NULL;

// Render stats
precise_t ps_prevframetime = 0;
precise_t ps_rendercalltime = 0;
precise_t ps_uitime = 0;
precise_t ps_swaptime = 0;

precise_t ps_bsptime = 0;

precise_t ps_sw_spritecliptime = 0;
precise_t ps_sw_portaltime = 0;
precise_t ps_sw_planetime = 0;
precise_t ps_sw_maskedtime = 0;

int ps_numbspcalls = 0;
int ps_numsprites = 0;
int ps_numdrawnodes = 0;
int ps_numpolyobjects = 0;

struct RenderStats g_renderstats;

static CV_PossibleValue_t drawdist_cons_t[] = {
	/*{256, "256"},*/	{512, "512"},	{768, "768"},
	{1024, "1024"},	{1536, "1536"},	{2048, "2048"},
	{3072, "3072"},	{4096, "4096"},	{6144, "6144"},
	{8192, "8192"},	{0, "Infinite"},	{0, NULL}};

static CV_PossibleValue_t drawdist_precip_cons_t[] = {
	{256, "256"},	{512, "512"},	{768, "768"},
	{1024, "1024"},	{1536, "1536"},	{2048, "2048"},
	{0, "None"},	{0, NULL}};

static CV_PossibleValue_t fov_cons_t[] = {{60*FRACUNIT, "MIN"}, {179*FRACUNIT, "MAX"}, {0, NULL}};
static CV_PossibleValue_t translucenthud_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}};
static CV_PossibleValue_t maxportals_cons_t[] = {{0, "MIN"}, {MAXPORTALS_CAP, "MAX"}, {0, NULL}}; // lmao rendering 32 portals, you're a card
static CV_PossibleValue_t homremoval_cons_t[] = {{0, "No"}, {1, "Yes"}, {2, "Flash"}, {0, NULL}};
static CV_PossibleValue_t secbright_cons_t[] = {{0, "MIN"}, {255, "MAX"}, {0, NULL}};

static void Fov_OnChange(void);

// if enabled, load all graphics at level load
consvar_t cv_precachetextures = CVAR_INIT ("precachetextures", "On", CV_SAVE, CV_OnOff, NULL);

consvar_t cv_chasecam[MAXSPLITSCREENPLAYERS] = {
	CVAR_INIT ("chasecam", "On", 0, CV_OnOff, NULL),
	CVAR_INIT ("chasecam2", "On", 0, CV_OnOff, NULL),
	CVAR_INIT ("chasecam3", "On", 0, CV_OnOff, NULL),
	CVAR_INIT ("chasecam4", "On", 0, CV_OnOff, NULL)
};

consvar_t cv_flipcam[MAXSPLITSCREENPLAYERS] = {
	CVAR_INIT ("flipcam", "Off", CV_CALL|CV_SAVE, CV_OnOff, weaponPrefChange),
	CVAR_INIT ("flipcam2", "Off", CV_CALL|CV_SAVE, CV_OnOff, weaponPrefChange2),
	CVAR_INIT ("flipcam3", "Off", CV_CALL|CV_SAVE, CV_OnOff, weaponPrefChange3),
	CVAR_INIT ("flipcam4", "Off", CV_CALL|CV_SAVE, CV_OnOff, weaponPrefChange4)
};

consvar_t cv_shadow = CVAR_INIT ("dropshadow", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_skybox = CVAR_INIT ("skybox", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_ffloorclip = CVAR_INIT ("ffloorclip", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_allowmlook = CVAR_INIT ("allowmlook", "Yes", CV_NETVAR, CV_YesNo, NULL);
consvar_t cv_showhud = CVAR_INIT ("showhud", "Yes", CV_CALL,  CV_YesNo, R_SetViewSize);
consvar_t cv_translucenthud = CVAR_INIT ("translucenthud", "10", CV_SAVE, translucenthud_cons_t, NULL);

consvar_t cv_drawdist = CVAR_INIT ("drawdist", "8192", CV_SAVE, drawdist_cons_t, NULL);
consvar_t cv_drawdist_precip = CVAR_INIT ("drawdist_precip", "1024", CV_SAVE, drawdist_precip_cons_t, NULL);

consvar_t cv_playerfade = CVAR_INIT ("playerfade", "Off", CV_SAVE, CV_OnOff, NULL);

consvar_t cv_fov[MAXSPLITSCREENPLAYERS] = {
	CVAR_INIT ("fov", "90", CV_FLOAT|CV_CALL|CV_SAVE, fov_cons_t, Fov_OnChange),
	CVAR_INIT ("fov2", "90", CV_FLOAT|CV_CALL|CV_SAVE, fov_cons_t, Fov_OnChange),
	CVAR_INIT ("fov3", "90", CV_FLOAT|CV_CALL|CV_SAVE, fov_cons_t, Fov_OnChange),
	CVAR_INIT ("fov4", "90", CV_FLOAT|CV_CALL|CV_SAVE, fov_cons_t, Fov_OnChange)
};

// Okay, whoever said homremoval causes a performance hit should be shot.
consvar_t cv_homremoval = CVAR_INIT ("homremoval", "Yes", CV_SAVE, homremoval_cons_t, NULL);

consvar_t cv_secbright = CVAR_INIT ("r_secbright", "0", CV_SAVE, secbright_cons_t, NULL);

consvar_t cv_maxportals = CVAR_INIT ("maxportals", "2", CV_SAVE, maxportals_cons_t, NULL);

consvar_t cv_renderstats = CVAR_INIT ("renderstats", "Off", 0, CV_OnOff, NULL);

consvar_t cv_sloperoll = CVAR_INIT ("spritesloperoll", "On", CV_SAVE, CV_OnOff, NULL);

consvar_t cv_sliptidetilt = CVAR_INIT ("sliptidetilt", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_nulldriftefx = CVAR_INIT ("nulldriftefx", "On", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_nulldrifttilt = CVAR_INIT ("nulldrifttilt", "On", CV_SAVE, CV_OnOff, NULL);

consvar_t cv_fakerollangle = CVAR_INIT ("fakerollangle", "Off", CV_SAVE, CV_OnOff, NULL);

consvar_t cv_affineprescale = CVAR_INIT ("affineprescale", "Off", CV_SAVE, CV_OnOff, NULL);
consvar_t cv_affinemosaic = CVAR_INIT ("affinemosaic", "Off", CV_SAVE, CV_OnOff, NULL);

void SplitScreen_OnChange(void)
{
	UINT8 i;

	/*
	local splitscreen is updated before you're in a game,
	so this is the first value for renderer splitscreen
	*/
	r_splitscreen = splitscreen;

	// recompute screen size
	R_ExecuteSetViewSize();

	if (!demo.playback)
	{
		for (i = 1; i < MAXSPLITSCREENPLAYERS; i++)
		{
			if (i > splitscreen)
				CL_RemoveSplitscreenPlayer(displayplayers[i]);
			else
				CL_AddSplitscreenPlayer();
		}

		if (server && !netgame)
			multiplayer = splitscreen;
	}
	else
	{
		for (i = 1; i < MAXSPLITSCREENPLAYERS; i++)
			displayplayers[i] = consoleplayer;

		for (i = 0; i < MAXPLAYERS; i++)
		{
			if (playeringame[i] && i != consoleplayer)
			{
				UINT8 j;
				for (j = 1; j < MAXSPLITSCREENPLAYERS; j++)
				{
					if (displayplayers[j] == consoleplayer)
					{
						displayplayers[j] = i;
						break;
					}
				}

				if (j == MAXSPLITSCREENPLAYERS)
					break;
			}
		}
	}
}

void Fov_OnChange(void)
{
	R_SetViewSize();
}

//
// R_PointOnSide
// Traverse BSP (sub) tree,
// check point against partition plane.
// Returns side 0 (front) or 1 (back).
//
// killough 5/2/98: reformatted
//
INT32 R_PointOnSideUDMF(fixed_t x, fixed_t y, const node_t *node)
{
	if (!node->dx)
		return x <= node->x ? node->dy > 0 : node->dy < 0;

	if (!node->dy)
		return y <= node->y ? node->dx < 0 : node->dx > 0;

	fixed_t dx = (x >> 1) - (node->x >> 1);
	fixed_t dy = (y >> 1) - (node->y >> 1);

	// Try to quickly decide by looking at sign bits.
	INT32 mask = (((node->dy ^ node->dx ^ dx ^ dy) < 0) ? -1 : 0);
	return (mask & ((node->dy ^ dx) < 0)) |  // (left is negative)
		   (~mask & (FixedMul(dy, node->dx>>FRACBITS) >= FixedMul(node->dy>>FRACBITS, dx)));
}

INT32 R_PointOnSideKart(fixed_t x, fixed_t y, const node_t *node)
{
	if (!node->dx)
		return x <= node->x ? node->dy > 0 : node->dy < 0;

	if (!node->dy)
		return y <= node->y ? node->dx < 0 : node->dx > 0;

	x -= node->x;
	y -= node->y;

	// Try to quickly decide by looking at sign bits.
	// also use a mask to avoid branch prediction
	INT32 mask = (((node->dy ^ node->dx ^ x ^ y) < 0) ? -1 : 0);
	return (mask & ((node->dy ^ x) < 0)) |  // (left is negative)
		   (~mask & (FixedMul(y, node->dx>>FRACBITS) >= FixedMul(node->dy>>FRACBITS, x)));
}

// killough 5/2/98: reformatted
INT32 R_PointOnSegSide(fixed_t x, fixed_t y, const seg_t *line)
{
	fixed_t lx = line->v1->x;
	fixed_t ly = line->v1->y;
	fixed_t ldx = line->v2->x - lx;
	fixed_t ldy = line->v2->y - ly;

	if (!ldx)
		return x <= lx ? ldy > 0 : ldy < 0;

	if (!ldy)
		return y <= ly ? ldx < 0 : ldx > 0;

	fixed_t dx = (x >> 1) - (lx >> 1);
	fixed_t dy = (y >> 1) - (ly >> 1);

	// Try to quickly decide by looking at sign bits.
	if ((ldy ^ ldx ^ dx ^ dy) < 0)
		return (ldy ^ dx) < 0;          // (left is negative)
	return FixedMul(dy, ldx>>FRACBITS) >= FixedMul(ldy>>FRACBITS, dx);
}

// Similar to R_PointToAngle, but requires an additional player_t argument.
// If this player is a local displayplayer, this will base off the calculations off of their camera instead, otherwise use viewx/viewy as usual.
// Yes this is kinda ghetto.
angle_t R_PointToAnglePlayer(player_t *player, fixed_t x, fixed_t y)
{
	fixed_t refx = viewx, refy = viewy;
	camera_t *cam = NULL;
	UINT8 i;

	for (i = 0; i < r_splitscreen; i++)
	{
		if (player == &players[displayplayers[i]])
		{
			cam = &camera[i];
			break;
		}
	}

	// use whatever cam we found's coordinates.
	if (cam != NULL)
	{
		refx = cam->x;
		refy = cam->y;
	}

	return R_PointToAngle2(refx, refy, x, y);
}

// This version uses 64-bit variables to avoid overflows with large values.
// Currently used only by OpenGL rendering.
angle_t R_PointToAngle64(INT64 x, INT64 y)
{
	return (y -= viewy, (x -= viewx) || y) ?
	x >= 0 ?
	y >= 0 ?
		(x > y) ? tantoangle[SlopeDivEx(y,x)] :                            // octant 0
		ANGLE_90-tantoangle[SlopeDivEx(x,y)] :                               // octant 1
		x > (y = -y) ? 0-tantoangle[SlopeDivEx(y,x)] :                    // octant 8
		ANGLE_270+tantoangle[SlopeDivEx(x,y)] :                              // octant 7
		y >= 0 ? (x = -x) > y ? ANGLE_180-tantoangle[SlopeDivEx(y,x)] :  // octant 3
		ANGLE_90 + tantoangle[SlopeDivEx(x,y)] :                             // octant 2
		(x = -x) > (y = -y) ? ANGLE_180+tantoangle[SlopeDivEx(y,x)] :    // octant 4
		ANGLE_270-tantoangle[SlopeDivEx(x,y)] :                              // octant 5
		0;
}

angle_t R_PointToAngle2(fixed_t pviewx, fixed_t pviewy, fixed_t x, fixed_t y)
{
	return (y -= pviewy, (x -= pviewx) || y) ?
	x >= 0 ?
	y >= 0 ?
		(x > y) ? tantoangle[SlopeDiv(y,x)] :                          // octant 0
		ANGLE_90-tantoangle[SlopeDiv(x,y)] :                           // octant 1
		x > (y = -y) ? 0-tantoangle[SlopeDiv(y,x)] :                   // octant 8
		ANGLE_270+tantoangle[SlopeDiv(x,y)] :                          // octant 7
		y >= 0 ? (x = -x) > y ? ANGLE_180-tantoangle[SlopeDiv(y,x)] :  // octant 3
		ANGLE_90 + tantoangle[SlopeDiv(x,y)] :                         // octant 2
		(x = -x) > (y = -y) ? ANGLE_180+tantoangle[SlopeDiv(y,x)] :    // octant 4
		ANGLE_270-tantoangle[SlopeDiv(x,y)] :                          // octant 5
		0;
}

#define ANGLE_180_DOUBLE (static_cast<double>(ANGLE_180))
#define ANGLE_360_DOUBLE (ANGLE_180_DOUBLE * 2)

// For absolute accuracy. Please don't use this outside of renderers.
angle_t R_PointToAngleFloat2(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2)
{
	double deltax = FixedToDouble(x2 - x1);
	double deltay = FixedToDouble(y2 - y1);

	// Converted to degrees
	double _arctan = (atan2(deltay, deltax) * ANGLE_180_DOUBLE) / DOUBLE_PI;

	while (_arctan < 0)
	{
		_arctan += ANGLE_360_DOUBLE;
	}

	return static_cast<angle_t>(_arctan);
}

#undef ANGLE_180_DOUBLE
#undef ANGLE_360_DOUBLE

//
// R_ScaleFromGlobalAngle
// Returns the texture mapping scale for the current line (horizontal span)
//  at the given angle.
// rw_distance must be calculated first.
//
// killough 5/2/98: reformatted, cleaned up
//
// note: THIS IS USED ONLY FOR WALLS!
fixed_t R_ScaleFromGlobalAngle(angle_t visangle)
{
	angle_t anglea = ANGLE_90 + (visangle-viewangle);
	angle_t angleb = ANGLE_90 + (visangle-rw_normalangle);
	fixed_t den = FixedMul(rw_distance, FINESINE(anglea>>ANGLETOFINESHIFT));
	// proff 11/06/98: Changed for high-res
	fixed_t num = FixedMul(projectiony[viewssnum],
			FINESINE(angleb>>ANGLETOFINESHIFT));

	if (den > num>>16)
	{
		num = FixedDiv(num, den);
		if (num > 64*FRACUNIT)
			return 64*FRACUNIT;
		if (num < 256)
			return 256;
		return num;
	}
	return 64*FRACUNIT;
}

//
// R_DoCulling
// Checks viewz and top/bottom heights of an item against culling planes
// Returns true if the item is to be culled, i.e it shouldn't be drawn!
// if args[1] is set, the camera view is required to be in the same area for culling to occur
boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixed_t bottomh, fixed_t toph)
{
	fixed_t cullplane;

	if (!cullheight)
		return false;

	cullplane = cullheight->frontsector->floorheight;
	if (cullheight->args[1]) // Group culling
	{
		if (!viewcullheight)
			return false;

		// Make sure this is part of the same group
		if (viewcullheight->frontsector == cullheight->frontsector)
		{
			// OK, we can cull
			if (vz > cullplane && toph < cullplane) // Cull if below plane
				return true;

			if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
				return true;
		}
	}
	else // Quick culling
	{
		if (vz > cullplane && toph < cullplane) // Cull if below plane
			return true;

		if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
			return true;
	}

	return false;
}

// Returns search dimensions within a blockmap, in the direction of viewangle and out to a certain distance.
void R_GetRenderBlockMapDimensions(fixed_t drawdist, INT32 *xl, INT32 *xh, INT32 *yl, INT32 *yh)
{
	const angle_t left = viewangle - clipangle[viewssnum];
	const angle_t right = viewangle + clipangle[viewssnum];

	const fixed_t vxleft = viewx + FixedMul(drawdist, FCOS(left));
	const fixed_t vyleft = viewy + FixedMul(drawdist, FSIN(left));

	const fixed_t vxright = viewx + FixedMul(drawdist, FCOS(right));
	const fixed_t vyright = viewy + FixedMul(drawdist, FSIN(right));

	// Try to narrow the search to within only the field of view
	*xl = (unsigned)(std::min(viewx, std::min(vxleft, vxright)) - bmaporgx)>>MAPBLOCKSHIFT;
	*xh = (unsigned)(std::max(viewx, std::max(vxleft, vxright)) - bmaporgx)>>MAPBLOCKSHIFT;
	*yl = (unsigned)(std::min(viewy, std::min(vyleft, vyright)) - bmaporgy)>>MAPBLOCKSHIFT;
	*yh = (unsigned)(std::max(viewy, std::max(vyleft, vyright)) - bmaporgy)>>MAPBLOCKSHIFT;

	if (*xh >= bmapwidth)
		*xh = bmapwidth - 1;

	if (*yh >= bmapheight)
		*yh = bmapheight - 1;

	BMBOUNDFIX(*xl, *xh, *yl, *yh);
}

//
// R_InitTextureMapping
//
static void R_InitTextureMapping(int s)
{
	INT32 i;
	INT32 x;
	INT32 t;
	fixed_t focallength;

	// Use tangent table to generate viewangletox:
	//  viewangletox will give the next greatest x
	//  after the view angle.
	//
	// Calc focallength
	//  so FIELDOFVIEW angles covers SCREENWIDTH.
	focallength = FixedDiv(projection[s],
		FINETANGENT(FINEANGLES/4+FIELDOFVIEW/2));

	focallengthf[s] = FIXED_TO_FLOAT(focallength);

	for (i = 0; i < FINEANGLES/2; i++)
	{
		if (FINETANGENT(i) > fovtan[s]*2)
			t = -1;
		else if (FINETANGENT(i) < -fovtan[s]*2)
			t = viewwidth+1;
		else
		{
			t = FixedMul(FINETANGENT(i), focallength);
			t = (centerxfrac - t+FRACUNIT-1)>>FRACBITS;

			if (t < -1)
				t = -1;
			else if (t > viewwidth+1)
				t = viewwidth+1;
		}
		viewangletox[s][i] = t;
	}

	// Scan viewangletox[] to generate xtoviewangle[]:
	//  xtoviewangle will give the smallest view angle
	//  that maps to x.
	for (x = 0; x <= viewwidth;x++)
	{
		i = 0;
		while (viewangletox[s][i] > x)
			i++;
		xtoviewangle[s][x] = (i<<ANGLETOFINESHIFT) - ANGLE_90;
	}

	// Take out the fencepost cases from viewangletox.
	for (i = 0; i < FINEANGLES/2; i++)
	{
		if (viewangletox[s][i] == -1)
			viewangletox[s][i] = 0;
		else if (viewangletox[s][i] == viewwidth+1)
			viewangletox[s][i]  = viewwidth;
	}

	clipangle[s] = xtoviewangle[s][0];
	doubleclipangle[s] = clipangle[s]*2;
}



//
// R_InitLightTables
// Only inits the zlight table,
//  because the scalelight table changes with view size.
//
#define DISTMAP 2

static inline void R_InitLightTables(void)
{
	INT32 i;
	INT32 j;
	INT32 level;
	INT32 startmapl;
	INT32 scale;

	// Calculate the light levels to use
	//  for each level / distance combination.
	for (i = 0; i < LIGHTLEVELS; i++)
	{
		startmapl = ((LIGHTLEVELS-1-i)*2)*NUMCOLORMAPS/LIGHTLEVELS;
		for (j = 0; j < MAXLIGHTZ; j++)
		{
			//added : 02-02-98 : use BASEVIDWIDTH, vid.width is not set already,
			// and it seems it needs to be calculated only once.
			scale = FixedDiv((BASEVIDWIDTH/2*FRACUNIT), (j+1)<<LIGHTZSHIFT);
			scale >>= LIGHTSCALESHIFT;
			level = startmapl - scale/DISTMAP;

			if (level < 0)
				level = 0;

			if (level >= NUMCOLORMAPS)
				level = NUMCOLORMAPS-1;

			zlight[i][j] = colormaps + level*256;
		}
	}
}

//#define WOUGHMP_WOUGHMP // I got a fish-eye lens - I'll make a rap video with a couple of friends
// it's kinda laggy sometimes

#ifdef WOUGHMP_WOUGHMP
#define AHHHH_IM_SO_MAAAAD { 0U, 0, FRACUNIT, NULL, 0, 0, {0}, {0}, false }
#else
#define AHHHH_IM_SO_MAAAAD { 0U,    FRACUNIT, NULL, 0, 0, {0}, {0}, false }
#endif

static struct viewmorph {
	angle_t rollangle; // pre-shifted by fineshift
#ifdef WOUGHMP_WOUGHMP
	fixed_t fisheye;
#endif

	fixed_t zoomneeded;
	INT32 *scrmap;
	INT32 scrmapsize;

	INT32 x1; // clip rendering horizontally for efficiency
	INT16 ceilingclip[MAXVIDWIDTH], floorclip[MAXVIDWIDTH];

	boolean use;
} viewmorph[MAXSPLITSCREENPLAYERS] = {
	AHHHH_IM_SO_MAAAAD,
	AHHHH_IM_SO_MAAAAD,
	AHHHH_IM_SO_MAAAAD,
	AHHHH_IM_SO_MAAAAD,
};

#undef AHHHH_IM_SO_MAAAAD

void R_CheckViewMorph(int s)
{
	struct viewmorph * v = &viewmorph[s];

	float zoomfactor, rollcos, rollsin;
	float x1, y1, x2, y2;
	fixed_t temp;
	INT32 end, vx, vy, pos, usedpos;
	INT32 realend;
	INT32 usedx, usedy;

	INT32 width = vid.width;
	INT32 height = vid.height;

	INT32 halfwidth;
	INT32 halfheight;

#ifdef WOUGHMP_WOUGHMP
	float fisheyemap[MAXVIDWIDTH/2 + 1];
#endif

	angle_t rollangle = viewroll;
#ifdef WOUGHMP_WOUGHMP
	fixed_t fisheye = cv_cam2_turnmultiplier.value; // temporary test value
#endif

	rollangle >>= ANGLETOFINESHIFT;
	rollangle = ((rollangle+2) & ~3) & FINEMASK; // Limit the distinct number of angles to reduce recalcs from angles changing a lot.

#ifdef WOUGHMP_WOUGHMP
	fisheye &= ~0x7FF; // Same
#endif

	if (r_splitscreen > 0)
	{
		height /= 2;

		if (r_splitscreen > 1)
		{
			width /= 2;
		}
	}

	halfwidth = width / 2;
	halfheight = height / 2;

	if (rollangle == v->rollangle &&
#ifdef WOUGHMP_WOUGHMP
		fisheye == v->fisheye &&
#endif
		v->scrmapsize == width * height)
		return; // No change

	v->rollangle = rollangle;
#ifdef WOUGHMP_WOUGHMP
	v->fisheye = fisheye;
#endif

	if (v->rollangle == 0
#ifdef WOUGHMP_WOUGHMP
		 && v->fisheye == 0
#endif
	 )
	{
		v->use = false;
		v->x1 = 0;
		if (v->zoomneeded != FRACUNIT)
			R_SetViewSize();
		v->zoomneeded = FRACUNIT;

		return;
	}

	if (v->scrmapsize != width * height)
	{
		if (v->scrmap)
			free(v->scrmap);
		v->scrmap = static_cast<INT32*>(malloc(width * height * sizeof(INT32)));
		v->scrmapsize = width * height;
	}

	temp = FINECOSINE(rollangle);
	rollcos = FIXED_TO_FLOAT(temp);
	temp = FINESINE(rollangle);
	rollsin = FIXED_TO_FLOAT(temp);

	// Calculate maximum zoom needed
	x1 = (width  * fabsf(rollcos) + height * fabsf(rollsin)) / width;
	y1 = (height * fabsf(rollcos) + width  * fabsf(rollsin)) / height;

#ifdef WOUGHMP_WOUGHMP
	if (fisheye)
	{
		float f = FIXED_TO_FLOAT(fisheye);
		for (vx = 0; vx <= halfwidth; vx++)
			fisheyemap[vx] = 1.0f / cos(atan(vx * f / halfwidth));

		f = cos(atan(f));
		if (f < 1.0f)
		{
			x1 /= f;
			y1 /= f;
		}
	}
#endif

	temp = std::max(x1, y1)*FRACUNIT;
	if (temp < FRACUNIT)
		temp = FRACUNIT;
	else
		temp |= 0x3FFF; // Limit how many times the viewport needs to be recalculated

	//CONS_Printf("Setting zoom to %f\n", FIXED_TO_FLOAT(temp));

	if (temp != v->zoomneeded)
	{
		v->zoomneeded = temp;
		R_SetViewSize();
	}

	zoomfactor = FIXED_TO_FLOAT(v->zoomneeded);

	realend = end = width * height - 1;

	if (r_splitscreen > 1)
	{
		realend = ( realend << 1 ) - width;
	}

	pos = 0;

	// Pre-multiply rollcos and rollsin to use for positional stuff
	rollcos /= zoomfactor;
	rollsin /= zoomfactor;

	x1 = -(halfwidth * rollcos - halfheight * rollsin);
	y1 = -(halfheight * rollcos + halfwidth * rollsin);

#ifdef WOUGHMP_WOUGHMP
	if (fisheye)
		v->x1 = (INT32)(halfwidth - (halfwidth * fabsf(rollcos) + halfheight * fabsf(rollsin)) * fisheyemap[halfwidth]);
	else
#endif
	v->x1 = (INT32)(halfwidth - (halfwidth * fabsf(rollcos) + halfheight * fabsf(rollsin)));
	//CONS_Printf("saving %d cols\n", v->x1);

	// Set ceilingclip and floorclip
	for (vx = 0; vx < width; vx++)
	{
		v->ceilingclip[vx] = height;
		v->floorclip[vx] = -1;
	}
	x2 = x1;
	y2 = y1;
	for (vx = 0; vx < width; vx++)
	{
		INT16 xa, ya, xb, yb;
		xa = x2+halfwidth;
		ya = y2+halfheight-1;
		xb = width-1-xa;
		yb = height-1-ya;

		v->ceilingclip[xa] = std::min(v->ceilingclip[xa], ya);
		v->floorclip[xa] = std::max(v->floorclip[xa], ya);
		v->ceilingclip[xb] = std::min(v->ceilingclip[xb], yb);
		v->floorclip[xb] = std::max(v->floorclip[xb], yb);
		x2 += rollcos;
		y2 += rollsin;
	}
	x2 = x1;
	y2 = y1;
	for (vy = 0; vy < height; vy++)
	{
		INT16 xa, ya, xb, yb;
		xa = x2+halfwidth;
		ya = y2+halfheight;
		xb = width-1-xa;
		yb = height-1-ya;

		v->ceilingclip[xa] = std::min(v->ceilingclip[xa], ya);
		v->floorclip[xa] = std::max(v->floorclip[xa], ya);
		v->ceilingclip[xb] = std::min(v->ceilingclip[xb], yb);
		v->floorclip[xb] = std::max(v->floorclip[xb], yb);
		x2 -= rollsin;
		y2 += rollcos;
	}

	//CONS_Printf("Top left corner is %f %f\n", x1, y1);

#ifdef WOUGHMP_WOUGHMP
	if (fisheye)
	{
		for (vy = 0; vy < halfheight; vy++)
		{
			x2 = x1;
			y2 = y1;
			x1 -= rollsin;
			y1 += rollcos;

			for (vx = 0; vx < width; vx++)
			{
				usedx = halfwidth + x2*fisheyemap[(int) floorf(fabsf(y2*zoomfactor))];
				usedy = halfheight + y2*fisheyemap[(int) floorf(fabsf(x2*zoomfactor))];

				usedpos = usedx + usedy*width;

				v->scrmap[pos] = usedpos;
				v->scrmap[end-pos] = end-usedpos;

				x2 += rollcos;
				y2 += rollsin;
				pos++;
			}
		}
	}
	else
	{
#endif
	x1 += halfwidth;
	y1 += halfheight;

	for (vy = 0; vy < halfheight; vy++)
	{
		x2 = x1;
		y2 = y1;
		x1 -= rollsin;
		y1 += rollcos;

		for (vx = 0; vx < width; vx++)
		{
			usedx = x2;
			usedy = y2;

			usedpos = usedx + usedy * vid.width;

			v->scrmap[pos] = usedpos;
			v->scrmap[end-pos] = realend-usedpos;

			x2 += rollcos;
			y2 += rollsin;
			pos++;
		}
	}
#ifdef WOUGHMP_WOUGHMP
	}
#endif

	v->use = true;
}

void R_ApplyViewMorph(int s)
{
	UINT8 *tmpscr = vid.screens[4];
	UINT8 *srcscr = vid.screens[0];
	INT32 width = vid.width;
	INT32 height = vid.height;
	INT32 p;
	INT32 end;

	if (!viewmorph[s].use)
		return;

	if (r_splitscreen == 1)
	{
		height /= 2;

		if (s == 1)
		{
			srcscr += vid.width * height;
		}
	}
	else if (r_splitscreen > 1)
	{
		width /= 2;
		height /= 2;

		if (s % 2)
		{
			srcscr += width;
		}

		if (s > 1)
		{
			srcscr += vid.width * height;
		}
	}

	end = width * height;

	for (p = 0; p < end; p++)
	{
		tmpscr[p] = srcscr[viewmorph[s].scrmap[p]];
	}

	VID_BlitLinearScreen(tmpscr, srcscr, width, height, width, vid.width);
}

angle_t R_ViewRollAngle(const player_t *player, UINT8 viewnum)
{
	angle_t roll = player->viewrollangle;

	if (cv_tilting.value)
	{
		if (!player->spectator && !camera[viewnum].freecam)
		{
			roll += player->tilt;
		}

		if (cv_actionmovie.value)
		{
			int xs = intsign(quake.x),
				 ys = intsign(quake.y),
				 zs = intsign(quake.z);
			roll += (xs ^ ys ^ zs) * ANG1;
		}
	}

	return roll;
}


//
// R_SetViewSize
// Do not really change anything here,
// because it might be in the middle of a refresh.
// The change will take effect next refresh.
//
boolean setsizeneeded;

void R_SetViewSize(void)
{
	setsizeneeded = true;
}

//
// R_ExecuteSetViewSize
//
void R_ExecuteSetViewSize(void)
{
	fixed_t dy;
	INT32 i;
	INT32 j;
	INT32 level;
	INT32 startmapl;
	angle_t fov;
	int s;

	setsizeneeded = false;

	if (rendermode == render_none)
		return;

	// status bar overlay
	st_overlay = cv_showhud.value;

	viewwidth = vid.width;
	viewheight = vid.height;

	if (r_splitscreen)
		viewheight >>= 1;

	if (r_splitscreen > 1)
	{
		viewwidth >>= 1;
	}

	centerx = viewwidth/2;
	centery = viewheight/2;
	centerxfrac = centerx<<FRACBITS;
	centeryfrac = centery<<FRACBITS;

	for (s = 0; s <= r_splitscreen; ++s)
	{
		fov = FixedAngle(cv_fov[s].value/2) + ANGLE_90;
		fovtan[s] = FixedMul(FINETANGENT(fov >> ANGLETOFINESHIFT), viewmorph[s].zoomneeded);
		if (r_splitscreen == 1) // Splitscreen FOV should be adjusted to maintain expected vertical view
			fovtan[s] = 17*fovtan[s]/10;

		projection[s] = projectiony[s] = FixedDiv(centerxfrac, fovtan[s]);

		R_InitTextureMapping(s);

		// planes
		if (rendermode == render_soft)
		{
			// this is only used for planes rendering in software mode
			j = viewheight*16;
			for (i = 0; i < j; i++)
			{
				dy = ((i - viewheight*8)<<FRACBITS);
				dy = FixedMul(abs(dy), fovtan[s]);
				yslopetab[s][i] = FixedDiv(centerx*FRACUNIT, dy);
			}
		}
	}

	R_InitViewBuffer();

	// thing clipping
	for (i = 0; i < viewwidth; i++)
		screenheightarray[i] = (INT16)viewheight;

	// setup sky scaling
	R_SetSkyScale();

	// planes
	if (rendermode == render_soft)
	{
		Z_Free(ds_su);
		Z_Free(ds_sv);
		Z_Free(ds_sz);
		ds_su = ds_sv = ds_sz = NULL;
	}

	memset(scalelight, 0xFF, sizeof(scalelight));

	// Calculate the light levels to use for each level/scale combination.
	for (i = 0; i< LIGHTLEVELS; i++)
	{
		startmapl = ((LIGHTLEVELS - 1 - i)*2)*NUMCOLORMAPS/LIGHTLEVELS;
		for (j = 0; j < MAXLIGHTSCALE; j++)
		{
			level = startmapl - j*vid.width/(viewwidth)/DISTMAP;

			if (level < 0)
				level = 0;

			if (level >= NUMCOLORMAPS)
				level = NUMCOLORMAPS - 1;

			scalelight[i][j] = colormaps + level*256;
		}
	}

	// continue to do the software setviewsize as long as we use the reference software view
#ifdef HWRENDER
	if (rendermode != render_soft)
		HWR_SetViewSize();
#endif

	am_recalc = true;
}

//
// R_Init
//

void R_Init(void)
{
	// screensize independent
	//I_OutputMsg("\nR_InitData");
	//R_InitData(); -- split to d_main for its own startup steps since it takes AGES
	CONS_Printf("R_InitColormaps()...\n");
	R_InitColormaps();

	R_SetViewSize(); // setsizeneeded is set true

	//I_OutputMsg("\nR_InitPlanes");
	R_InitPlanes();

	// this is now done by SCR_Recalc() at the first mode set
	//I_OutputMsg("\nR_InitLightTables");
	R_InitLightTables();

	//I_OutputMsg("\nR_InitTranslucencyTables\n");
	//R_InitTranslucencyTables();

	R_InitDrawNodes();

	framecount = 0;
}

//
// R_IsPointInSector
//
boolean R_IsPointInSector(sector_t *sector, fixed_t x, fixed_t y)
{
	size_t i;
	size_t passes = 0;

	for (i = 0; i < sector->linecount; i++)
	{
		line_t *line = sector->lines[i];
		vertex_t *v1, *v2;

		if (line->frontsector == line->backsector)
			continue;

		v1 = line->v1;
		v2 = line->v2;

		// make sure v1 is below v2
		if (v1->y > v2->y)
		{
			vertex_t *tmp = v1;
			v1 = v2;
			v2 = tmp;
		}
		else if (v1->y == v2->y)
			// horizontal line, we can't match this
			continue;

		if (v1->y < y && y <= v2->y)
		{
			// if the y axis in inside the line, find the point where we intersect on the x axis...
			fixed_t vx = v1->x + (INT64)(v2->x - v1->x) * (y - v1->y) / (v2->y - v1->y);

			// ...and if that point is to the left of the point, count it as inside.
			if (vx < x)
				passes++;
		}
	}

	// and odd number of passes means we're inside the polygon.
	return passes % 2;
}

//
// R_PointInSubsector
//
subsector_t *R_PointInSubsector(fixed_t x, fixed_t y)
{
	size_t nodenum = numnodes-1;

	while (!(nodenum & NF_SUBSECTOR))
		nodenum = nodes[nodenum].children[R_PointOnSide(x, y, nodes+nodenum)];

	return &subsectors[nodenum & ~NF_SUBSECTOR];
}

//
// R_PointInSubsectorOrNull, same as above but returns 0 if not in subsector
//
subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y)
{
	node_t *node;
	INT32 side, i;
	size_t nodenum;
	subsector_t *ret;
	seg_t *seg;

	// single subsector is a special case
	if (numnodes == 0)
		return subsectors;

	nodenum = numnodes - 1;

	while (!(nodenum & NF_SUBSECTOR))
	{
		node = &nodes[nodenum];
		side = R_PointOnSide(x, y, node);
		nodenum = node->children[side];
	}

	ret = &subsectors[nodenum & ~NF_SUBSECTOR];
	for (i = 0, seg = &segs[ret->firstline]; i < ret->numlines; i++, seg++)
	{
		if (seg->glseg)
			continue;

		//if (R_PointOnSegSide(x, y, seg)) -- breaks in ogl because polyvertex_t cast over vertex pointers
		if (P_PointOnLineSide(x, y, seg->linedef) != seg->side)
			return 0;
	}

	return ret;
}

//
// R_SetupFrame
//

static void
R_SetupCommonFrame
(		player_t * player,
		subsector_t * subsector)
{
	const UINT8 viewnum = R_GetViewNumber();
	newview->player = player;

	newview->x += quake.x;
	newview->y += quake.y;
	newview->z += quake.z;

	newview->roll = R_ViewRollAngle(player, viewnum);

	if (subsector)
		newview->sector = subsector->sector;
	else
		newview->sector = R_PointInSubsector(newview->x, newview->y)->sector;

	R_InterpolateView(R_GetTimeFrac(RTF_CAMERA));
}

static void R_SetupAimingFrame(int s)
{
	player_t *player = &players[displayplayers[s]];
	camera_t *thiscam = &camera[s];

	if (player->awayviewtics && player->awayviewmobj
	 && thiscam && !thiscam->freecam) // dont force this if we wanna freecam!
	{
		newview->aim = player->awayviewaiming;
		newview->angle = player->awayviewmobj->angle;
	}
	else if (thiscam && thiscam->chase)
	{
		newview->aim = thiscam->aiming;
		newview->angle = thiscam->angle;
	}
	else if (P_IsLocalPlayer(player) && !demo.playback && player->playerstate != PST_DEAD)
	{
		newview->aim = localaiming[s];
		newview->angle = localangle[s];
	}
	else
	{
		newview->aim = player->aiming;
		newview->angle = player->mo->angle;
	}
}

void R_SetupFrame(int s, boolean skybox)
{
	player_t *player = &players[displayplayers[s]];
	camera_t *thiscam = &camera[s];
	boolean chasecam = (cv_chasecam[s].value != 0);

	R_SetViewContext(static_cast<viewcontext_e>(VIEWCONTEXT_PLAYER1 + s));

	if (player->spectator)
	{
		// Free flying spectator uses demo freecam. This
		// requires chasecam to be enabled.
		chasecam = true;
	}

	if (chasecam && (thiscam && !thiscam->chase))
	{
		P_ResetCamera(player, thiscam);
		thiscam->chase = true;
	}
	else if (!chasecam)
		thiscam->chase = false;

	newview->sky = (mapnamespace != MNS_SRB2KART) ? false : !skybox; // force this to false for non kart maps

	R_SetupAimingFrame(s);

	if (player->awayviewtics && player->awayviewmobj // cut-away view stuff
	 && thiscam && !thiscam->freecam) // dont force this when we wanna freecam!
	{
		r_viewmobj = player->awayviewmobj; // should be a MT_ALTVIEWMAN      whos altview man?
		I_Assert(r_viewmobj != NULL);

		newview->x = r_viewmobj->x;
		newview->y = r_viewmobj->y;
		newview->z = r_viewmobj->z + 20*FRACUNIT;

		R_SetupCommonFrame(player, r_viewmobj->subsector);
	}
	else if (chasecam)
	// use outside cam view
	{
		r_viewmobj = NULL;

		newview->x = thiscam->x;
		newview->y = thiscam->y;
		newview->z = thiscam->z + (thiscam->height>>1);

		R_SetupCommonFrame(player, thiscam->subsector);
	}
	else
	// use the player's eyes view
	{
		r_viewmobj = player->mo;
		I_Assert(r_viewmobj != NULL);

		newview->x = r_viewmobj->x;
		newview->y = r_viewmobj->y;
		newview->z = player->viewz;

		R_SetupCommonFrame(player, r_viewmobj->subsector);
	}
}

void R_SkyboxFrame(int s)
{
	player_t *player = &players[displayplayers[s]];
	camera_t *thiscam = &camera[s];

	R_SetViewContext(static_cast<viewcontext_e>(VIEWCONTEXT_SKY1 + s));

	// cut-away view stuff
	newview->sky = true;
	r_viewmobj = skyboxmo[0];
#ifdef PARANOIA
	if (!r_viewmobj)
	{
		const size_t playeri = (size_t)(player - players);
		I_Error("R_SkyboxFrame: r_viewmobj null (player %s)", sizeu1(playeri));
	}
#endif

	R_SetupAimingFrame(s);

	newview->angle += r_viewmobj->angle;

	newview->x = r_viewmobj->x;
	newview->y = r_viewmobj->y;

	if (mapnamespace == MNS_SRB2KART)
    {
		newview->z = (r_viewmobj->spawnpoint) ? (((fixed_t)r_viewmobj->spawnpoint->angle)<<FRACBITS) : 0;
    }
	else
		newview->z = r_viewmobj->z; // 26/04/17: use actual Z position instead of spawnpoint angle!

	if (mapheaderinfo[gamemap-1])
	{
		mapheader_t *mh = mapheaderinfo[gamemap-1];
		vector3_t campos = {0,0,0}; // Position of player's actual view point

		if (player->awayviewtics) {
			campos.x = player->awayviewmobj->x;
			campos.y = player->awayviewmobj->y;
			campos.z = player->awayviewmobj->z + 20*FRACUNIT;
		} else if (thiscam->chase) {
			campos.x = thiscam->x;
			campos.y = thiscam->y;
			campos.z = thiscam->z + (thiscam->height>>1);
		} else {
			campos.x = player->mo->x;
			campos.y = player->mo->y;
			campos.z = player->viewz;
		}

		// Earthquake effects should be scaled in the skybox
		// (if an axis isn't used, the skybox won't shake in that direction)
		campos.x += quake.x;
		campos.y += quake.y;
		campos.z += quake.z;

		if (skyboxmo[1]) // Is there a viewpoint?
		{
			fixed_t x = 0, y = 0;
			if (mh->skybox_scalex > 0)
				x = (campos.x - skyboxmo[1]->x) / mh->skybox_scalex;
			else if (mh->skybox_scalex < 0)
				x = (campos.x - skyboxmo[1]->x) * -mh->skybox_scalex;

			if (mh->skybox_scaley > 0)
				y = (campos.y - skyboxmo[1]->y) / mh->skybox_scaley;
			else if (mh->skybox_scaley < 0)
				y = (campos.y - skyboxmo[1]->y) * -mh->skybox_scaley;

			if (r_viewmobj->angle == 0)
			{
				newview->x += x;
				newview->y += y;
			}
			else if (r_viewmobj->angle == ANGLE_90)
			{
				newview->x -= y;
				newview->y += x;
			}
			else if (r_viewmobj->angle == ANGLE_180)
			{
				newview->x -= x;
				newview->y -= y;
			}
			else if (r_viewmobj->angle == ANGLE_270)
			{
				newview->x += y;
				newview->y -= x;
			}
			else
			{
				angle_t ang = r_viewmobj->angle>>ANGLETOFINESHIFT;
				newview->x += FixedMul(x,FINECOSINE(ang)) - FixedMul(y,  FINESINE(ang));
				newview->y += FixedMul(x,  FINESINE(ang)) + FixedMul(y,FINECOSINE(ang));
			}
		}
		if (mh->skybox_scalez > 0)
			newview->z += campos.z / mh->skybox_scalez;
		else if (mh->skybox_scalez < 0)
			newview->z += campos.z * -mh->skybox_scalez;
	}

	R_SetupCommonFrame(player, r_viewmobj->subsector);
}

boolean R_ViewpointHasChasecam(player_t *player)
{
	boolean chasecam = false;
	UINT8 i;

	for (i = 0; i <= splitscreen; i++)
	{
		if (player == &players[g_localplayers[i]])
		{
			chasecam = (cv_chasecam[i].value != 0);
			break;
		}
	}

	if (player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode || player->spectator)
		chasecam = true; // force chasecam on

	return chasecam;
}

boolean R_IsViewpointThirdPerson(player_t *player, boolean skybox)
{
	boolean chasecam = false;

	// Prevent game crash if player is ever invalid.
	if (!player)
		return false;

	chasecam = R_ViewpointHasChasecam(player);

	// cut-away view stuff
	if (player->awayviewtics || skybox)
		return chasecam;
	// use outside cam view
	else if (chasecam)
		return true;

	// use the player's eyes view
	return false;
}

static void R_PortalFrame(portal_t *portal)
{
	viewx = portal->viewx;
	viewy = portal->viewy;
	viewz = portal->viewz;

	viewangle = portal->viewangle;
	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);

	portalclipstart = portal->start;
	portalclipend = portal->end;

	if (portal->clipline != -1)
	{
		portalclipline = &lines[portal->clipline];
		portalcullsector = portalclipline->frontsector;
		viewsector = portalclipline->frontsector;
	}
	else
	{
		portalclipline = NULL;
		portalcullsector = NULL;
		viewsector = R_PointInSubsectorFast(viewx, viewy)->sector;
	}
}

static void Mask_Pre(maskcount_t* m)
{
	m->drawsegs[0] = ds_p - drawsegs;
	m->vissprites[0] = visspritecount;
	m->viewx = viewx;
	m->viewy = viewy;
	m->viewz = viewz;
	m->viewsector = viewsector;
}

static void Mask_Post(maskcount_t* m)
{
	m->drawsegs[1] = ds_p - drawsegs;
	m->vissprites[1] = visspritecount;
}

// ================
// R_RenderView
// ================

// viewx, viewy, viewangle, all that good stuff must be set
static void R_RenderViewpoint(maskcount_t* mask, INT32 cachenum, boolean drawprecip)
{
	Mask_Pre(mask);

	curdrawsegs = ds_p;

	R_RenderFirstBSPNode(cachenum);

	// dont draw precip in skyboxes!
	if (drawprecip)
		R_AddPrecipitationSprites();

	Mask_Post(mask);
}

//                     FAB NOTE FOR WIN32 PORT !! I'm not finished already,
// but I suspect network may have problems with the video buffer being locked
// for all duration of rendering, and being released only once at the end..
// I mean, there is a win16lock() or something that lasts all the rendering,
// so maybe we should release screen lock before each netupdate below..?

void R_RenderPlayerView(void)
{
	INT32         nummasks = 1;
	maskcount_t*  masks    = static_cast<maskcount_t*>(malloc(sizeof(maskcount_t)));
	const boolean skybox   = (skyboxmo[0] && cv_skybox.value);
	const boolean oldsky   = (skybox && mapnamespace == MNS_SRB2KART);

	// using sky portals on kart maps usually results in pretty bad performance
	skyVisible = oldsky && skyVisiblePerPlayer[viewssnum];

	if (skyVisible)
	{
		R_SkyboxFrame(viewssnum);
		R_ClearClipSegs();
		R_ClearDrawSegs();
		R_ClearSegTables();
		R_ClearPlanes();
		R_ClearSprites();
		R_SetColumnContext(COLUMNCONTEXT_FLUSH);
		R_RenderViewpoint(&masks[nummasks - 1], nummasks - 1, false);
		R_ClipSprites(drawsegs, NULL);
		R_ResetColumnBuffer();
		R_SetColumnContext(COLUMNCONTEXT_DIRECT);
		R_DrawSkyPlanes();
		R_DrawPlanes();
		R_DrawMasked(masks, nummasks);
	}

	R_SetupFrame(viewssnum, skybox);
	skyVisible = false;
	framecount++;
	validcount++;

	memset(&g_renderstats, 0, sizeof g_renderstats);

	// Clear buffers.
	R_ClearPlanes();
	if (viewmorph[viewssnum].use)
	{
		portalclipstart = viewmorph[viewssnum].x1;
		portalclipend = viewwidth-viewmorph[viewssnum].x1-1;
		R_PortalClearClipSegs(portalclipstart, portalclipend);
		memcpy(ceilingclip, viewmorph[viewssnum].ceilingclip, sizeof(INT16)*vid.width);
		memcpy(floorclip,   viewmorph[viewssnum].floorclip,   sizeof(INT16)*vid.width);
	}
	else
	{
		portalclipstart = 0;
		portalclipend = viewwidth;
		R_ClearClipSegs();
	}
	R_ClearDrawSegs();
	R_ClearSegTables();
	R_ClearSprites();
	Portal_InitList();

	// check for new console commands.
	NetUpdate();

	// The head node is the last node output.
	R_SetColumnContext(COLUMNCONTEXT_FLUSH);
	ps_numbspcalls = ps_numpolyobjects = ps_numdrawnodes = 0;
	ps_bsptime = I_GetPreciseTime();
	R_RenderViewpoint(&masks[nummasks - 1], nummasks - 1, true);
	ps_bsptime = I_GetPreciseTime() - ps_bsptime;

	ps_sw_spritecliptime = I_GetPreciseTime();
	R_ClipSprites(drawsegs, NULL);
	ps_sw_spritecliptime = I_GetPreciseTime() - ps_sw_spritecliptime;
	R_ResetColumnBuffer();

	// Add skybox portals caused by sky visplanes.
	if (skybox && !oldsky)
		Portal_AddSkyboxPortals();

	// Portal rendering. Hijacks the BSP traversal.
	ps_sw_portaltime = I_GetPreciseTime();
	if (portal_base && !cv_debugrender_portal.value)
	{
		// tp_sema = srb2::g_main_threadpool->end_sema();
		// srb2::g_main_threadpool->notify_sema(tp_sema);
		// srb2::g_main_threadpool->wait_sema(tp_sema);
		// srb2::g_main_threadpool->begin_sema();

		portal_t *portal;

		for (portal = portal_base; portal; portal = portal_base)
		{
			portalrender = portal->pass; // Recursiveness depth.

			R_ClearFFloorClips();

			// Apply the viewpoint stored for the portal.
			R_PortalFrame(portal);

			// Hack in the clipsegs to delimit the starting
			// clipping for sprites and possibly other similar
			// future items.
			R_PortalClearClipSegs(portal->start, portal->end);

			// Hack in the top/bottom clip values for the window
			// that were previously stored.
			Portal_ClipApply(portal);

			validcount++;

			masks = static_cast<maskcount_t*>(realloc(masks, (++nummasks)*sizeof(maskcount_t)));

			// Render the BSP from the new viewpoint, and clip
			// any sprites with the new clipsegs and window.
			R_RenderViewpoint(&masks[nummasks - 1], nummasks - 1, !portal->isskybox);

			R_ClipSprites(ds_p - (masks[nummasks - 1].drawsegs[1] - masks[nummasks - 1].drawsegs[0]), portal);

			R_ResetColumnBuffer();

			Portal_Remove(portal);
		}

		// tp_sema = srb2::g_main_threadpool->end_sema();
		// srb2::g_main_threadpool->notify_sema(tp_sema);
		// srb2::g_main_threadpool->wait_sema(tp_sema);
		// srb2::g_main_threadpool->begin_sema();
	}
	ps_sw_portaltime = I_GetPreciseTime() - ps_sw_portaltime;

	R_SetColumnContext(COLUMNCONTEXT_DIRECT);
	ps_sw_planetime = I_GetPreciseTime();
	R_DrawSkyPlanes();
	R_DrawPlanes();
	ps_sw_planetime = I_GetPreciseTime() - ps_sw_planetime;

	// draw mid texture and sprite
	// And now 3D floors/sides!
	ps_sw_maskedtime = I_GetPreciseTime();
	R_DrawMasked(masks, nummasks);
	ps_sw_maskedtime = I_GetPreciseTime() - ps_sw_maskedtime;

	if (cv_debugrender_visplanes.value)
	{
		for (INT32 i = 0; i < MAXVISPLANES; i++)
		{
			for (visplane_t* pl = visplanes[i]; pl; pl = pl->next)
			{
				if (pl->minx > pl->maxx)
					continue;

				auto col = [](int x, int top, int bot)
				{
					if (top > bot)
						std::swap(top, bot);
					if (top < 0)
						top = 0;
					if (bot > viewheight-1)
						bot = viewheight-1;

					UINT8* p =  R_Address(x, top);

					while (top <= bot)
					{
						*p = 35;
						p += vid.width;
						top++;
					}
				};

				auto span = [col](int x, int top, int bot)
				{
					if (top <= bot)
						col(x, top, bot);
				};

				INT32 top = pl->top[pl->minx];
				INT32 bottom = pl->bottom[pl->minx];

				span(pl->minx, top, bottom);
				span(pl->maxx, pl->top[pl->maxx], pl->bottom[pl->maxx]);

				for (INT32 x = pl->minx + 1; x < std::min(pl->maxx, viewwidth); ++x)
				{
					INT32 new_top = pl->top[x];
					INT32 new_bottom = pl->bottom[x];

					if (new_top > new_bottom)
						continue;

					if (top > bottom)
					{
						col(x, new_top, new_top);
						col(x, new_bottom, new_bottom);
					}
					else
					{
						col(x, top, new_top);
						col(x, bottom, new_bottom);
					}

					top = new_top;
					bottom = new_bottom;
				}
			}
		}
	}

	// debugrender_portal: fill portals with red, draw over everything
	if (portal_base && cv_debugrender_portal.value)
	{
		portal_t *portal;
		constexpr UINT8 pal = 0x23; // red

		for (portal = portal_base; portal; portal = portal_base)
		{
			INT32 i;
			INT32 width = (portal->end - portal->start);

			for (i = 0; i < std::min(width, viewwidth); ++i)
			{
				INT32 yl = std::max(portal->ceilingclip[i] + 1, 0);
				INT32 yh = std::min(static_cast<INT32>(portal->floorclip[i]), viewheight);

				for (; yl < yh; ++yl)
				{
					*R_Address(0, portal->start + i + yl) = pal;
				}
			}

			Portal_Remove(portal);
		}
	}

	// save value to skyVisiblePerPlayer
	// this is so that P1 can't affect whether P2 can see a skybox or not, or vice versa
	skyVisiblePerPlayer[viewssnum] = (!oldsky || skyVisible); // if we dont draw the sky using the old method, always mark this as true

	free(masks);
}

// =========================================================================
//                    ENGINE COMMANDS & VARS
// =========================================================================

void R_RegisterEngineStuff(void)
{
	UINT8 i;

	CV_RegisterVar(&cv_gravity);
	CV_RegisterVar(&cv_allowmlook);
	CV_RegisterVar(&cv_homremoval);

#ifdef SCRAMBLE_REMOVED
	CV_RegisterVar(&cv_scrambleremoved);
#endif
	// Enough for dedicated server
	if (dedicated)
		return;

	CV_RegisterVar(&cv_precachetextures);

	CV_RegisterVar(&cv_drawdist);
	CV_RegisterVar(&cv_drawdist_precip);

	CV_RegisterVar(&cv_shadow);
	CV_RegisterVar(&cv_skybox);
	CV_RegisterVar(&cv_ffloorclip);
	CV_RegisterVar(&cv_secbright);

	for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
	{
		CV_RegisterVar(&cv_fov[i]);
		CV_RegisterVar(&cv_chasecam[i]);
		CV_RegisterVar(&cv_flipcam[i]);
		CV_RegisterVar(&cv_cam_dist[i]);
		CV_RegisterVar(&cv_cam_still[i]);
		CV_RegisterVar(&cv_cam_height[i]);
		CV_RegisterVar(&cv_cam_speed[i]);
		CV_RegisterVar(&cv_cam_rotate[i]);
	}

	CV_RegisterVar(&cv_tilting);
	CV_RegisterVar(&cv_actionmovie);
	CV_RegisterVar(&cv_sloperoll);
	CV_RegisterVar(&cv_sliptidetilt);
	CV_RegisterVar(&cv_nulldriftefx);
	CV_RegisterVar(&cv_nulldrifttilt);
	CV_RegisterVar(&cv_fakerollangle);
	CV_RegisterVar(&cv_affineprescale);
	CV_RegisterVar(&cv_affinemosaic);

	CV_RegisterVar(&cv_showhud);
	CV_RegisterVar(&cv_translucenthud);

	CV_RegisterVar(&cv_maxportals);

	CV_RegisterVar(&cv_playerfade);

	CV_RegisterVar(&cv_movebob);

	// Frame interpolation/uncapped
	CV_RegisterVar(&cv_fpscap);

	//CV_RegisterVar(&cv_drawpickups);

	CV_RegisterVar(&cv_debugfinishline);

	// debugging

	CV_RegisterVar(&cv_debugrender_contrast);
	CV_RegisterVar(&cv_debugrender_spriteclip);
	CV_RegisterVar(&cv_debugrender_portal);
	CV_RegisterVar(&cv_debugrender_visplanes);
	CV_RegisterVar(&cv_debugrender_freezebsp);

	COM_AddCommand("debugrender_highlight", Command_Debugrender_highlight);
}
