//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2016 Magnus Norddahl
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//

#include <stdlib.h>
#include <stddef.h>


#include "doomdef.h"
#include "doomstat.h"
#include "doomdata.h"
#include "p_lnspec.h"
#include "r_sky.h"
#include "v_video.h"
#include "m_swap.h"
#include "filesystem.h"
#include "stats.h"
#include "a_sharedglobal.h"
#include "d_net.h"
#include "g_level.h"
#include "r_wallsetup.h"
#include "v_palette.h"
#include "r_data/colormaps.h"
#include "r_walldraw.h"
#include "r_memory.h"
#include "swrenderer/line/r_line.h"
#include "swrenderer/scene/r_scene.h"
#include "swrenderer/scene/r_light.h"
#include "swrenderer/viewport/r_viewport.h"

namespace swrenderer
{
	// Transform and clip coordinates. Returns true if it was clipped away
	bool FWallCoords::Init(RenderThread* thread, const DVector2& pt1, const DVector2& pt2, seg_t* lineseg)
	{
		auto viewport = thread->Viewport.get();
		RenderPortal* renderportal = thread->Portal.get();

		// Rotate to view direction:

		tleft.X = float(pt1.X * viewport->viewpoint.Sin - pt1.Y * viewport->viewpoint.Cos);
		tright.X = float(pt2.X * viewport->viewpoint.Sin - pt2.Y * viewport->viewpoint.Cos);

		tleft.Y = float(pt1.X * viewport->viewpoint.TanCos + pt1.Y * viewport->viewpoint.TanSin);
		tright.Y = float(pt2.X * viewport->viewpoint.TanCos + pt2.Y * viewport->viewpoint.TanSin);

		if (renderportal->MirrorFlags & RF_XFLIP)
		{
			float t = -tleft.X;
			tleft.X = -tright.X;
			tright.X = t;
			std::swap(tleft.Y, tright.Y);
		}

		// Edge clip:

		float fsx1, fsz1, fsx2, fsz2;

		if (tleft.X >= -tleft.Y)
		{
			if (tleft.X > tleft.Y) return true;	// left edge is off the right side
			if (tleft.Y == 0) return true;
			fsx1 = viewport->CenterX + tleft.X * viewport->CenterX / tleft.Y;
			fsz1 = tleft.Y;
			tx1 = 0.0f;
		}
		else
		{
			if (tright.X < -tright.Y) return true;	// wall is off the left side
			float den = tleft.X - tright.X - tright.Y + tleft.Y;
			if (den == 0) return true;
			fsx1 = 0;
			tx1 = (tleft.X + tleft.Y) / den;
			fsz1 = tleft.Y + (tright.Y - tleft.Y) * tx1;
		}

		if (fsz1 < TOO_CLOSE_Z)
			return true;

		if (tright.X <= tright.Y)
		{
			if (tright.X < -tright.Y) return true;	// right edge is off the left side
			if (tright.Y == 0) return true;
			fsx2 = viewport->CenterX + tright.X * viewport->CenterX / tright.Y;
			fsz2 = tright.Y;
			tx2 = 1.0f;
		}
		else
		{
			if (tleft.X > tleft.Y) return true;	// wall is off the right side
			float den = tright.Y - tleft.Y - tright.X + tleft.X;
			if (den == 0) return true;
			fsx2 = viewwidth;
			tx2 = (tleft.X - tleft.Y) / den;
			fsz2 = tleft.Y + (tright.Y - tleft.Y) * tx2;
		}

		if (fsz2 < TOO_CLOSE_Z)
			return true;

		// Find screen range covered by the line:

		sx1 = xs_RoundToInt(fsx1);
		sx2 = xs_RoundToInt(fsx2);

		if (sx2 <= sx1)
			return true;

		// Remap texture coordinates to the part covered by the line segment:

		if (lineseg && lineseg->linedef)
		{
			line_t* line = lineseg->linedef;
			float t1, t2;
			if (fabs(line->delta.X) > fabs(line->delta.Y))
			{
				t1 = (lineseg->v1->fX() - line->v1->fX()) / line->delta.X;
				t2 = (lineseg->v2->fX() - line->v1->fX()) / line->delta.X;
			}
			else
			{
				t1 = (lineseg->v1->fY() - line->v1->fY()) / line->delta.Y;
				t2 = (lineseg->v2->fY() - line->v1->fY()) / line->delta.Y;
			}

			if (line->sidedef[0] != lineseg->sidedef)
			{
				t1 = 1.0f - t1;
				t2 = 1.0f - t2;
			}

			tx1 = t1 + tx1 * (t2 - t1);
			tx2 = t1 + tx2 * (t2 - t1);
		}

		// Calculate screen depths for the start and end points (resulting values are at the pixel center):

		float delta = fsx2 - fsx1;
		float t1 = (sx1 + 0.5f - fsx1) / delta;
		float t2 = (sx2 + 0.5f - fsx1) / delta;
		float invZ1 = 1.0f / fsz1;
		float invZ2 = 1.0f / fsz2;
		sz1 = 1.0f / (invZ1 * (1.0f - t1) + invZ2 * t1);
		sz2 = 1.0f / (invZ1 * (1.0f - t2) + invZ2 * t2);

		// Adjust texture coordinates to also be at the pixel centers:

		float ftx1 = tx1 * invZ1;
		float ftx2 = tx2 * invZ2;
		tx1 = (ftx1 * (1.0f - t1) + ftx2 * t1) * sz1;
		tx2 = (ftx1 * (1.0f - t2) + ftx2 * t2) * sz2;

		return false;
	}

	////////////////////////////////////////////////////////////////////////////

	ProjectedWallCull ProjectedWallLine::Project(RenderViewport *viewport, double z, const FWallCoords *wallc)
	{
		return Project(viewport, z, z, wallc);
	}

	ProjectedWallCull ProjectedWallLine::Project(RenderViewport *viewport, double z1, double z2, const FWallCoords *wallc)
	{
		float y1 = (float)(viewport->CenterY - z1 * viewport->InvZtoScale / wallc->sz1);
		float y2 = (float)(viewport->CenterY - z2 * viewport->InvZtoScale / wallc->sz2);

		if (y1 < 0 && y2 < 0) // entire line is above screen
		{
			memset(&ScreenY[wallc->sx1], 0, (wallc->sx2 - wallc->sx1) * sizeof(ScreenY[0]));
			return ProjectedWallCull::OutsideAbove;
		}
		else if (y1 > viewheight && y2 > viewheight) // entire line is below screen
		{
			fillshort(&ScreenY[wallc->sx1], wallc->sx2 - wallc->sx1, viewheight);
			return ProjectedWallCull::OutsideBelow;
		}

		if (wallc->sx2 <= wallc->sx1)
			return ProjectedWallCull::Visible;

		float rcp_delta = 1.0f / (wallc->sx2 - wallc->sx1);
		if (y1 >= 0.0f && y2 >= 0.0f && xs_RoundToInt(y1) <= viewheight && xs_RoundToInt(y2) <= viewheight)
		{
			for (int x = wallc->sx1; x < wallc->sx2; x++)
			{
				float t = (x - wallc->sx1) * rcp_delta;
				float y = y1 * (1.0f - t) + y2 * t;
				ScreenY[x] = (short)xs_RoundToInt(y);
			}
		}
		else
		{
			for (int x = wallc->sx1; x < wallc->sx2; x++)
			{
				float t = (x - wallc->sx1) * rcp_delta;
				float y = y1 * (1.0f - t) + y2 * t;
				ScreenY[x] = (short)clamp(xs_RoundToInt(y), 0, viewheight);
			}
		}

		return ProjectedWallCull::Visible;
	}

	ProjectedWallCull ProjectedWallLine::Project(RenderViewport *viewport, const secplane_t &plane, const FWallCoords *wallc, seg_t *curline, bool xflip)
	{
		if (!plane.isSlope())
		{
			return Project(viewport, plane.Zat0() - viewport->viewpoint.Pos.Z, wallc);
		}
		else
		{
			// Get Z coordinates at both ends of the line
			double x, y, den, z1, z2;
			if (xflip)
			{
				x = curline->v2->fX();
				y = curline->v2->fY();
				if (wallc->sx1 == 0 && 0 != (den = wallc->tleft.X - wallc->tright.X + wallc->tleft.Y - wallc->tright.Y))
				{
					double frac = (wallc->tleft.Y + wallc->tleft.X) / den;
					x -= frac * (x - curline->v1->fX());
					y -= frac * (y - curline->v1->fY());
				}
				z1 = plane.ZatPoint(x, y) - viewport->viewpoint.Pos.Z;

				if (wallc->sx2 > wallc->sx1 + 1)
				{
					x = curline->v1->fX();
					y = curline->v1->fY();
					if (wallc->sx2 == viewwidth && 0 != (den = wallc->tleft.X - wallc->tright.X - wallc->tleft.Y + wallc->tright.Y))
					{
						double frac = (wallc->tright.Y - wallc->tright.X) / den;
						x += frac * (curline->v2->fX() - x);
						y += frac * (curline->v2->fY() - y);
					}
					z2 = plane.ZatPoint(x, y) - viewport->viewpoint.Pos.Z;
				}
				else
				{
					z2 = z1;
				}
			}
			else
			{
				x = curline->v1->fX();
				y = curline->v1->fY();
				if (wallc->sx1 == 0 && 0 != (den = wallc->tleft.X - wallc->tright.X + wallc->tleft.Y - wallc->tright.Y))
				{
					double frac = (wallc->tleft.Y + wallc->tleft.X) / den;
					x += frac * (curline->v2->fX() - x);
					y += frac * (curline->v2->fY() - y);
				}
				z1 = plane.ZatPoint(x, y) - viewport->viewpoint.Pos.Z;

				if (wallc->sx2 > wallc->sx1 + 1)
				{
					x = curline->v2->fX();
					y = curline->v2->fY();
					if (wallc->sx2 == viewwidth && 0 != (den = wallc->tleft.X - wallc->tright.X - wallc->tleft.Y + wallc->tright.Y))
					{
						double frac = (wallc->tright.Y - wallc->tright.X) / den;
						x -= frac * (x - curline->v1->fX());
						y -= frac * (y - curline->v1->fY());
					}
					z2 = plane.ZatPoint(x, y) - viewport->viewpoint.Pos.Z;
				}
				else
				{
					z2 = z1;
				}
			}

			return Project(viewport, z1, z2, wallc);
		}
	}

	void ProjectedWallLine::ClipTop(int x1, int x2, const DrawSegmentClipInfo& clip)
	{
		for (int i = x1; i < x2; i++)
		{
			ScreenY[i] = std::max(ScreenY[i], clip.sprtopclip[i]);
		}
	}

	void ProjectedWallLine::ClipBottom(int x1, int x2, const DrawSegmentClipInfo& clip)
	{
		for (int i = x1; i < x2; i++)
		{
			ScreenY[i] = min(ScreenY[i], clip.sprbottomclip[i]);
		}
	}

	/////////////////////////////////////////////////////////////////////////

	void ProjectedWallTexcoords::ProjectTop(RenderViewport* viewport, sector_t* frontsector, sector_t* backsector, seg_t* lineseg, const FWallCoords& WallC, FSoftwareTexture* pic)
	{
		side_t* sidedef = lineseg->sidedef;
		line_t* linedef = lineseg->linedef;

		float yscale = GetYScale(sidedef, pic, side_t::top);
		double cameraZ = viewport->viewpoint.Pos.Z;

		double texturemid;
		if (yscale >= 0)
		{ // normal orientation
			if (linedef->flags & ML_DONTPEGTOP)
			{ // top of texture at top
				texturemid = (frontsector->GetPlaneTexZ(sector_t::ceiling) - cameraZ) * yscale;
			}
			else
			{ // bottom of texture at bottom
				texturemid = (backsector->GetPlaneTexZ(sector_t::ceiling) - cameraZ) * yscale + pic->GetHeight();
			}
		}
		else
		{ // upside down
			if (linedef->flags & ML_DONTPEGTOP)
			{ // bottom of texture at top
				texturemid = (frontsector->GetPlaneTexZ(sector_t::ceiling) - cameraZ) * yscale + pic->GetHeight();
			}
			else
			{ // top of texture at bottom
				texturemid = (backsector->GetPlaneTexZ(sector_t::ceiling) - cameraZ) * yscale;
			}
		}

		texturemid += GetRowOffset(lineseg, pic, side_t::top);
		fixed_t xoffset = GetXOffset(lineseg, pic, side_t::top);

		Project(viewport, sidedef->TexelLength * GetXScale(sidedef, pic, side_t::top), WallC, pic, xoffset, texturemid, yscale, false);
	}

	void ProjectedWallTexcoords::ProjectMid(RenderViewport* viewport, sector_t* frontsector, seg_t* lineseg, const FWallCoords& WallC, FSoftwareTexture* pic)
	{
		side_t* sidedef = lineseg->sidedef;
		line_t* linedef = lineseg->linedef;

		float yscale = GetYScale(sidedef, pic, side_t::mid);
		double cameraZ = viewport->viewpoint.Pos.Z;

		double texturemid;
		if (yscale >= 0)
		{ // normal orientation
			if (linedef->flags & ML_DONTPEGBOTTOM)
			{ // bottom of texture at bottom
				texturemid = (frontsector->GetPlaneTexZ(sector_t::floor) - cameraZ) * yscale + pic->GetHeight();
			}
			else
			{ // top of texture at top
				texturemid = (frontsector->GetPlaneTexZ(sector_t::ceiling) - cameraZ) * yscale;
			}
		}
		else
		{ // upside down
			if (linedef->flags & ML_DONTPEGBOTTOM)
			{ // top of texture at bottom
				texturemid = (frontsector->GetPlaneTexZ(sector_t::floor) - cameraZ) * yscale;
			}
			else
			{ // bottom of texture at top
				texturemid = (frontsector->GetPlaneTexZ(sector_t::ceiling) - cameraZ) * yscale + pic->GetHeight();
			}
		}

		texturemid += GetRowOffset(lineseg, pic, side_t::mid);
		fixed_t xoffset = GetXOffset(lineseg, pic, side_t::mid);

		Project(viewport, sidedef->TexelLength * GetXScale(sidedef, pic, side_t::mid), WallC, pic, xoffset, texturemid, yscale, false);
	}

	void ProjectedWallTexcoords::ProjectBottom(RenderViewport* viewport, sector_t* frontsector, sector_t* backsector, seg_t* lineseg, const FWallCoords& WallC, FSoftwareTexture* pic)
	{
		side_t* sidedef = lineseg->sidedef;
		line_t* linedef = lineseg->linedef;

		double frontlowertop = frontsector->GetPlaneTexZ(sector_t::ceiling);
		if (frontsector->GetTexture(sector_t::ceiling) == skyflatnum && backsector->GetTexture(sector_t::ceiling) == skyflatnum)
		{
			// Putting sky ceilings on the front and back of a line alters the way unpegged
			// positioning works.
			frontlowertop = backsector->GetPlaneTexZ(sector_t::ceiling);
		}

		float yscale = GetYScale(sidedef, pic, side_t::bottom);
		double cameraZ = viewport->viewpoint.Pos.Z;

		double texturemid;
		if (yscale >= 0)
		{ // normal orientation
			if (linedef->flags & ML_DONTPEGBOTTOM)
			{ // bottom of texture at bottom
				texturemid = (frontlowertop - cameraZ) * yscale;
			}
			else
			{ // top of texture at top
				texturemid = (backsector->GetPlaneTexZ(sector_t::floor) - cameraZ) * yscale;
			}
		}
		else
		{ // upside down
			if (linedef->flags & ML_DONTPEGBOTTOM)
			{ // top of texture at bottom
				texturemid = (frontlowertop - cameraZ) * yscale;
			}
			else
			{ // bottom of texture at top
				texturemid = (backsector->GetPlaneTexZ(sector_t::floor) - cameraZ) * yscale + pic->GetHeight();
			}
		}

		texturemid += GetRowOffset(lineseg, pic, side_t::bottom);
		fixed_t xoffset = GetXOffset(lineseg, pic, side_t::bottom);

		Project(viewport, sidedef->TexelLength * GetXScale(sidedef, pic, side_t::bottom), WallC, pic, xoffset, texturemid, yscale, false);
	}

	void ProjectedWallTexcoords::ProjectTranslucent(RenderViewport* viewport, sector_t* frontsector, sector_t* backsector, seg_t* lineseg, const FWallCoords& WallC, FSoftwareTexture* pic)
	{
		line_t* linedef = lineseg->linedef;
		side_t* sidedef = lineseg->sidedef;

		float yscale = GetYScale(sidedef, pic, side_t::mid);
		double cameraZ = viewport->viewpoint.Pos.Z;

		double texZFloor = max(frontsector->GetPlaneTexZ(sector_t::floor), backsector->GetPlaneTexZ(sector_t::floor));
		double texZCeiling = min(frontsector->GetPlaneTexZ(sector_t::ceiling), backsector->GetPlaneTexZ(sector_t::ceiling));

		double texturemid;
		if (yscale >= 0)
		{ // normal orientation
			if (linedef->flags & ML_DONTPEGBOTTOM)
			{ // bottom of texture at bottom
				texturemid = (texZFloor - cameraZ) * yscale + pic->GetHeight();
			}
			else
			{ // top of texture at top
				texturemid = (texZCeiling - cameraZ) * yscale;
			}
		}
		else
		{ // upside down
			if (linedef->flags & ML_DONTPEGBOTTOM)
			{ // top of texture at bottom
				texturemid = (texZFloor - cameraZ) * yscale;
			}
			else
			{ // bottom of texture at top
				texturemid = (texZCeiling - cameraZ) * yscale + pic->GetHeight();
			}
		}

		texturemid += GetRowOffset(lineseg, pic, side_t::mid);
		fixed_t xoffset = GetXOffset(lineseg, pic, side_t::mid);

		Project(viewport, sidedef->TexelLength * GetXScale(sidedef, pic, side_t::mid), WallC, pic, xoffset, texturemid, yscale, false);
	}

	void ProjectedWallTexcoords::Project3DFloor(RenderViewport* viewport, F3DFloor* rover, seg_t* lineseg, const FWallCoords& WallC, FSoftwareTexture* pic)
	{
		// find positioning
		side_t* scaledside;
		side_t::ETexpart scaledpart;
		if (rover->flags & FF_UPPERTEXTURE)
		{
			scaledside = lineseg->sidedef;
			scaledpart = side_t::top;
		}
		else if (rover->flags & FF_LOWERTEXTURE)
		{
			scaledside = lineseg->sidedef;
			scaledpart = side_t::bottom;
		}
		else
		{
			scaledside = rover->master->sidedef[0];
			scaledpart = side_t::mid;
		}

		double xscale = pic->GetScale().X * scaledside->GetTextureXScale(scaledpart);
		float yscale = pic->GetScale().Y * scaledside->GetTextureYScale(scaledpart);

		double rowoffset = lineseg->sidedef->GetTextureYOffset(side_t::mid) + rover->master->sidedef[0]->GetTextureYOffset(side_t::mid);
		double planez = rover->model->GetPlaneTexZ(sector_t::ceiling);

		fixed_t xoffset = xs_Fix<16>::ToFix(lineseg->sidedef->GetTextureXOffset(side_t::mid) + rover->master->sidedef[0]->GetTextureXOffset(side_t::mid));
		if (rowoffset < 0)
		{
			rowoffset += pic->GetHeight();
		}

		double texturemid = (planez - viewport->viewpoint.Pos.Z) * yscale;
		if (pic->useWorldPanning(lineseg->GetLevel()))
		{
			// rowoffset is added before the multiply so that the masked texture will
			// still be positioned in world units rather than texels.

			texturemid = texturemid + rowoffset * yscale;
			xoffset = xs_RoundToInt(xoffset * xscale);
		}
		else
		{
			// rowoffset is added outside the multiply so that it positions the texture
			// by texels instead of world units.
			texturemid += rowoffset;
		}

		Project(viewport, lineseg->sidedef->TexelLength * xscale, WallC, pic, xoffset, texturemid, yscale, false);
	}

	void ProjectedWallTexcoords::Project(RenderViewport *viewport, double walxrepeat, const FWallCoords& WallC, FSoftwareTexture* pic, fixed_t xoffset, double texturemid, float yscale, bool flipx)
	{
		float texwidth = pic->GetWidth();
		float texheight = pic->GetHeight();

		float texU1 = FIXED2FLOAT(xoffset) / texwidth;
		float texU2 = texU1 + walxrepeat / texwidth;
		if (walxrepeat < 0.0)
		{
			texU1 += 1.0f;
			texU2 += 1.0f;
		}
		if (flipx)
		{
			texU1 = 1.0f - texU1;
			texU2 = 1.0f - texU2;
		}

		float texV = texturemid / texheight;

		// Set up some fake vertices as that makes it easier to calculate the gradients using code already known to work.

		Vertex v1;
		v1.x = WallC.sx1 + 0.5f;// WallC.tleft.X;
		v1.y = 0.0f;
		v1.w = WallC.sz1;//WallC.tleft.Y;

		Vertex v2;
		v2.x = WallC.sx2 + 0.5f;// WallC.tright.X;
		v2.y = 0.0f;
		v2.w = WallC.sz2;// WallC.tright.Y;

		v1.u = texU1 * (1.0f - WallC.tx1) + texU2 * WallC.tx1;
		v2.u = texU1 * (1.0f - WallC.tx2) + texU2 * WallC.tx2;
		v1.v = texV;
		v2.v = texV;

		Vertex v3;
		v3.x = v1.x;
		v3.y = v1.y - 100.0f;
		v3.w = v1.w;
		v3.u = v1.u;
		v3.v = v1.v + yscale * 100.0f / texheight;

		// Project to screen space

		v1.w = 1.0f / v1.w;
		v2.w = 1.0f / v2.w;
		v3.w = 1.0f / v3.w;
		v1.y = viewport->CenterY - v1.y * v1.w * viewport->InvZtoScale;
		v2.y = viewport->CenterY - v2.y * v2.w * viewport->InvZtoScale;
		v3.y = viewport->CenterY - v3.y * v3.w * viewport->InvZtoScale;

		// Calculate gradients

		float bottomX = (v2.x - v3.x) * (v1.y - v3.y) - (v1.x - v3.x) * (v2.y - v3.y);
		float bottomY = (v1.x - v3.x) * (v2.y - v3.y) - (v2.x - v3.x) * (v1.y - v3.y);

		wstepX = FindGradientX(bottomX, 1.0f, 1.0f, 1.0f, v1, v2, v3);
		ustepX = FindGradientX(bottomX, v1.u, v2.u, v3.u, v1, v2, v3);
		vstepX = FindGradientX(bottomX, v1.v, v2.v, v3.v, v1, v2, v3);

		wstepY = FindGradientY(bottomY, 1.0f, 1.0f, 1.0f, v1, v2, v3);
		ustepY = FindGradientY(bottomY, v1.u, v2.u, v3.u, v1, v2, v3);
		vstepY = FindGradientY(bottomY, v1.v, v2.v, v3.v, v1, v2, v3);

		startX = v1.x;
		upos = v1.u * v1.w;
		vpos = v1.v * v1.w;
		wpos = v1.w;
	}

	double ProjectedWallTexcoords::GetRowOffset(seg_t* lineseg, FSoftwareTexture* tex, side_t::ETexpart texpart)
	{
		double yrepeat = GetYScale(lineseg->sidedef, tex, texpart);
		double rowoffset = lineseg->sidedef->GetTextureYOffset(texpart);
		if (yrepeat >= 0)
		{
			// check if top of texture at top:
			bool top_at_top =
				(texpart == side_t::top && (lineseg->linedef->flags & ML_DONTPEGTOP)) ||
				(texpart != side_t::top && !(lineseg->linedef->flags & ML_DONTPEGBOTTOM));

			if (rowoffset < 0 && top_at_top)
			{
				rowoffset += tex->GetHeight();
			}
		}
		else
		{
			rowoffset = -rowoffset;
		}

		if (tex->useWorldPanning(lineseg->GetLevel()))
		{
			return rowoffset * yrepeat;
		}
		else
		{
			// rowoffset is added outside the multiply so that it positions the texture
			// by texels instead of world units.
			return rowoffset;
		}
	}

	fixed_t ProjectedWallTexcoords::GetXOffset(seg_t* lineseg, FSoftwareTexture* tex, side_t::ETexpart texpart)
	{
		fixed_t TextureOffsetU = xs_Fix<16>::ToFix(lineseg->sidedef->GetTextureXOffset(texpart));
		double xscale = GetXScale(lineseg->sidedef, tex, texpart);

		fixed_t xoffset;
		if (tex->useWorldPanning(lineseg->GetLevel()))
		{
			xoffset = xs_RoundToInt(TextureOffsetU * xscale);
		}
		else
		{
			xoffset = TextureOffsetU;
		}

		if (xscale < 0)
		{
			xoffset = -xoffset;
		}

		return xoffset;
	}

	double ProjectedWallTexcoords::GetXScale(side_t* sidedef, FSoftwareTexture* tex, side_t::ETexpart texpart)
	{
		double TextureScaleU = sidedef->GetTextureXScale(texpart);
		return tex->GetScale().X * TextureScaleU;
	}

	double ProjectedWallTexcoords::GetYScale(side_t* sidedef, FSoftwareTexture* tex, side_t::ETexpart texpart)
	{
		double TextureScaleV = sidedef->GetTextureYScale(texpart);
		return tex->GetScale().Y * TextureScaleV;
	}

	/////////////////////////////////////////////////////////////////////////

	void ProjectedWallLight::SetLightLeft(RenderThread *thread, const FWallCoords &wallc)
	{
		spritelight = false;

		x1 = wallc.sx1;

		CameraLight *cameraLight = CameraLight::Instance();
		if (cameraLight->FixedColormap() == nullptr && cameraLight->FixedLightLevel() < 0)
		{
			lightleft = float(thread->Light->WallVis(wallc.sz1, foggy));
			lightstep = float((thread->Light->WallVis(wallc.sz2, foggy) - lightleft) / (wallc.sx2 - wallc.sx1));
		}
		else
		{
			lightleft = 1;
			lightstep = 0;
		}
	}

	void ProjectedWallLight::SetColormap(const sector_t *frontsector, seg_t *lineseg, int tier, lightlist_t *lit)
	{
		if (!lit)
		{
			basecolormap = GetColorTable(frontsector->Colormap, frontsector->SpecialColors[sector_t::walltop]);
			foggy = frontsector->Level->fadeto || frontsector->Colormap.FadeColor || (frontsector->Level->flags & LEVEL_HASFADETABLE);

			if (!(lineseg->sidedef->Flags & WALLF_POLYOBJ))
				lightlevel = lineseg->sidedef->GetLightLevel(foggy, frontsector->lightlevel, tier);
			else
				lightlevel = frontsector->GetLightLevel();
		}
		else
		{
			basecolormap = GetColorTable(lit->extra_colormap, frontsector->SpecialColors[sector_t::walltop]);
			foggy = frontsector->Level->fadeto || basecolormap->Fade || (frontsector->Level->flags & LEVEL_HASFADETABLE);
			lightlevel = lineseg->sidedef->GetLightLevel(foggy, *lit->p_lightlevel, tier, lit->lightsource != nullptr);
		}
	}
}
