/*
**  Polygon Doom software renderer
**  Copyright (c) 2016 Magnus Norddahl
**
**  This software is provided 'as-is', without any express or implied
**  warranty.  In no event will the authors be held liable for any damages
**  arising from the use of this software.
**
**  Permission is granted to anyone to use this software for any purpose,
**  including commercial applications, and to alter it and redistribute it
**  freely, subject to the following restrictions:
**
**  1. The origin of this software must not be misrepresented; you must not
**     claim that you wrote the original software. If you use this software
**     in a product, an acknowledgment in the product documentation would be
**     appreciated but is not required.
**  2. Altered source versions must be plainly marked as such, and must not be
**     misrepresented as being the original software.
**  3. This notice may not be removed or altered from any source distribution.
**
*/

#include <stdlib.h>
#include "templates.h"
#include "doomdef.h"
#include "doomstat.h"
#include "doomdata.h"
#include "p_lnspec.h"
#include "sbar.h"
#include "r_data/r_translate.h"
#include "poly_wall.h"
#include "poly_decal.h"
#include "polyrenderer/poly_renderer.h"
#include "r_sky.h"
#include "polyrenderer/scene/poly_light.h"
#include "polyrenderer/poly_renderthread.h"
#include "g_levellocals.h"
#include "a_dynlight.h"

EXTERN_CVAR(Bool, r_drawmirrors)
EXTERN_CVAR(Bool, r_fogboundary)

bool RenderPolyWall::RenderLine(PolyRenderThread *thread, seg_t *line, sector_t *frontsector, uint32_t subsectorDepth, uint32_t stencilValue, std::vector<PolyTranslucentObject*> &translucentWallsOutput, std::vector<std::unique_ptr<PolyDrawLinePortal>> &linePortals, size_t linePortalsStart, line_t *portalEnterLine)
{
	double frontceilz1 = frontsector->ceilingplane.ZatPoint(line->v1);
	double frontfloorz1 = frontsector->floorplane.ZatPoint(line->v1);
	double frontceilz2 = frontsector->ceilingplane.ZatPoint(line->v2);
	double frontfloorz2 = frontsector->floorplane.ZatPoint(line->v2);
	double topTexZ = frontsector->GetPlaneTexZ(sector_t::ceiling);
	double bottomTexZ = frontsector->GetPlaneTexZ(sector_t::floor);

	PolyDrawLinePortal *polyportal = nullptr;
	if (line->backsector == nullptr && line->linedef && line->sidedef == line->linedef->sidedef[0] && (line->linedef->special == Line_Mirror && r_drawmirrors))
	{
		if (portalEnterLine == line->linedef)
		{
			return false;
		}

		linePortals.push_back(std::unique_ptr<PolyDrawLinePortal>(new PolyDrawLinePortal(line->linedef)));
		polyportal = linePortals.back().get();
	}
	else if (line->linedef && line->linedef->isVisualPortal() && line->sidedef == line->linedef->sidedef[0])
	{
		if (portalEnterLine == line->linedef)
		{
			return false;
		}

		FLinePortal *portal = line->linedef->getPortal();
		for (size_t i = linePortalsStart; i < linePortals.size(); i++)
		{
			if (linePortals[i]->Portal == portal) // To do: what other criteria do we need to check for?
			{
				polyportal = linePortals[i].get();
				break;
			}
		}
		if (!polyportal)
		{
			linePortals.push_back(std::unique_ptr<PolyDrawLinePortal>(new PolyDrawLinePortal(portal)));
			polyportal = linePortals.back().get();
		}
	}

	RenderPolyWall wall;
	wall.LineSeg = line;
	wall.Line = line->linedef;
	wall.Side = line->sidedef;
	wall.LineSegLine = line->linedef;
	wall.Masked = false;
	wall.SubsectorDepth = subsectorDepth;
	wall.StencilValue = stencilValue;
	wall.SectorLightLevel = frontsector->lightlevel;

	if (line->backsector == nullptr)
	{
		if (line->sidedef)
		{
			wall.SetCoords(line->v1->fPos(), line->v2->fPos(), frontceilz1, frontfloorz1, frontceilz2, frontfloorz2);
			wall.TopTexZ = topTexZ;
			wall.BottomTexZ = bottomTexZ;
			wall.Wallpart = side_t::mid;
			wall.Colormap = GetColorTable(frontsector->Colormap, wall.Side->GetSpecialColor(wall.Wallpart, side_t::walltop, frontsector));
			wall.Texture = GetTexture(wall.Line, wall.Side, side_t::mid);
			wall.Polyportal = polyportal;
			wall.Render(thread);
			return true;
		}
	}
	else if (line->PartnerSeg && line->PartnerSeg->Subsector)
	{
		PolyTransferHeights fakeback(line->PartnerSeg->Subsector);
		sector_t *backsector = fakeback.FrontSector;

		double backceilz1 = backsector->ceilingplane.ZatPoint(line->v1);
		double backfloorz1 = backsector->floorplane.ZatPoint(line->v1);
		double backceilz2 = backsector->ceilingplane.ZatPoint(line->v2);
		double backfloorz2 = backsector->floorplane.ZatPoint(line->v2);

		double topceilz1 = frontceilz1;
		double topceilz2 = frontceilz2;
		double topfloorz1 = MAX(MIN(backceilz1, frontceilz1), frontfloorz1);
		double topfloorz2 = MAX(MIN(backceilz2, frontceilz2), frontfloorz2);
		double bottomceilz1 = MIN(MAX(frontfloorz1, backfloorz1), frontceilz1);
		double bottomceilz2 = MIN(MAX(frontfloorz2, backfloorz2), frontceilz2);
		double bottomfloorz1 = frontfloorz1;
		double bottomfloorz2 = frontfloorz2;
		double middleceilz1 = topfloorz1;
		double middleceilz2 = topfloorz2;
		double middlefloorz1 = MIN(bottomceilz1, middleceilz1);
		double middlefloorz2 = MIN(bottomceilz2, middleceilz2);

		bool bothSkyCeiling = frontsector->GetTexture(sector_t::ceiling) == skyflatnum && backsector->GetTexture(sector_t::ceiling) == skyflatnum;
		bool bothSkyFloor = frontsector->GetTexture(sector_t::floor) == skyflatnum && backsector->GetTexture(sector_t::floor) == skyflatnum;

		if ((topceilz1 > topfloorz1 || topceilz2 > topfloorz2) && line->sidedef && !bothSkyCeiling)
		{
			wall.SetCoords(line->v1->fPos(), line->v2->fPos(), topceilz1, topfloorz1, topceilz2, topfloorz2);
			wall.TopTexZ = topTexZ;
			wall.BottomTexZ = MIN(MIN(backceilz1, frontceilz1), MIN(backceilz2, frontceilz2));
			wall.Wallpart = side_t::top;
			wall.Colormap = GetColorTable(frontsector->Colormap, wall.Side->GetSpecialColor(wall.Wallpart, side_t::walltop, frontsector));
			wall.Texture = GetTexture(wall.Line, wall.Side, side_t::top);
			wall.Render(thread);
		}

		if ((bottomfloorz1 < bottomceilz1 || bottomfloorz2 < bottomceilz2) && line->sidedef && !bothSkyFloor)
		{
			wall.SetCoords(line->v1->fPos(), line->v2->fPos(), bottomceilz1, bottomfloorz1, bottomceilz2, bottomfloorz2);
			wall.TopTexZ = MAX(MAX(frontfloorz1, backfloorz1), MAX(frontfloorz2, backfloorz2));
			wall.BottomTexZ = bottomTexZ;
			wall.UnpeggedCeil1 = topceilz1;
			wall.UnpeggedCeil2 = topceilz2;
			wall.Wallpart = side_t::bottom;
			wall.Colormap = GetColorTable(frontsector->Colormap, wall.Side->GetSpecialColor(wall.Wallpart, side_t::walltop, frontsector));
			wall.Texture = GetTexture(wall.Line, wall.Side, side_t::bottom);
			wall.Render(thread);
		}

		if (line->sidedef)
		{
			wall.SetCoords(line->v1->fPos(), line->v2->fPos(), middleceilz1, middlefloorz1, middleceilz2, middlefloorz2);
			wall.TopTexZ = MAX(middleceilz1, middleceilz2);
			wall.BottomTexZ = MIN(middlefloorz1, middlefloorz2);
			wall.Wallpart = side_t::mid;
			wall.Colormap = GetColorTable(frontsector->Colormap, wall.Side->GetSpecialColor(wall.Wallpart, side_t::walltop, frontsector));
			wall.Texture = GetTexture(wall.Line, wall.Side, side_t::mid);
			wall.Masked = true;
			wall.Additive = !!(wall.Line->flags & ML_ADDTRANS);
			wall.Alpha = wall.Line->alpha;
			wall.FogBoundary = IsFogBoundary(frontsector, backsector);

			FTexture *midtex = TexMan.GetPalettedTexture(line->sidedef->GetTexture(side_t::mid), true);
			if ((midtex && midtex->isValid()) || wall.FogBoundary)
				translucentWallsOutput.push_back(thread->FrameMemory->NewObject<PolyTranslucentWall>(wall));

			if (polyportal)
			{
				wall.Polyportal = polyportal;
				wall.Render(thread);
			}
		}
	}
	return polyportal != nullptr;
}

bool RenderPolyWall::IsFogBoundary(sector_t *front, sector_t *back)
{
	return r_fogboundary && PolyCameraLight::Instance()->FixedColormap() == nullptr && front->Colormap.FadeColor &&
		front->Colormap.FadeColor != back->Colormap.FadeColor &&
		(front->GetTexture(sector_t::ceiling) != skyflatnum || back->GetTexture(sector_t::ceiling) != skyflatnum);
}

void RenderPolyWall::Render3DFloorLine(PolyRenderThread *thread, seg_t *line, sector_t *frontsector, uint32_t subsectorDepth, uint32_t stencilValue, F3DFloor *fakeFloor, std::vector<PolyTranslucentObject*> &translucentWallsOutput)
{
	if (!(fakeFloor->flags & FF_EXISTS)) return;
	if (!(fakeFloor->flags & FF_RENDERPLANES)) return;
	if (!fakeFloor->model) return;
	if (fakeFloor->alpha == 0) return;

	double frontceilz1 = fakeFloor->top.plane->ZatPoint(line->v1);
	double frontfloorz1 = fakeFloor->bottom.plane->ZatPoint(line->v1);
	double frontceilz2 = fakeFloor->top.plane->ZatPoint(line->v2);
	double frontfloorz2 = fakeFloor->bottom.plane->ZatPoint(line->v2);
	double topTexZ = fakeFloor->model->GetPlaneTexZ(sector_t::ceiling);
	double bottomTexZ = fakeFloor->model->GetPlaneTexZ(sector_t::floor);

	if (frontceilz1 <= frontfloorz1 || frontceilz2 <= frontfloorz2)
		return;

	if (fakeFloor->flags & FF_SWIMMABLE) // Only draw swimmable boundary if not swimmable on both sides
	{
		DVector2 c = (line->v1->fPos() + line->v2->fPos()) * 0.5;
		double cz = (frontceilz1 + frontceilz2 + frontfloorz1 + frontfloorz2) * 0.25;
		for (unsigned i = 0; i < frontsector->e->XFloor.ffloors.Size(); i++)
		{
			F3DFloor *frontFloor = frontsector->e->XFloor.ffloors[i];
			if (!(frontFloor->flags & FF_EXISTS)) continue;
			if (!(frontFloor->flags & FF_RENDERPLANES)) continue;
			if (!frontFloor->model) continue;
			if (frontFloor->alpha == 0) continue;
			if (frontFloor->top.plane->ZatPoint(c) >= cz && frontFloor->bottom.plane->ZatPoint(c) <= cz && (frontFloor->flags & FF_SWIMMABLE))
			{
				return;
			}
		}
	}

	RenderPolyWall wall;
	wall.LineSeg = line;
	wall.LineSegLine = line->linedef;
	wall.Line = fakeFloor->master;
	wall.Side = fakeFloor->master->sidedef[0];
	wall.Additive = !!(fakeFloor->flags & FF_ADDITIVETRANS);
	if (!wall.Additive && fakeFloor->alpha == 255)
	{
		wall.Masked = false;
		wall.Alpha = 1.0;
	}
	else
	{
		wall.Masked = true;
		wall.Alpha = fakeFloor->alpha / 255.0;
	}
	wall.SubsectorDepth = subsectorDepth;
	wall.StencilValue = stencilValue;
	wall.SetCoords(line->v1->fPos(), line->v2->fPos(), frontceilz1, frontfloorz1, frontceilz2, frontfloorz2);
	wall.TopTexZ = topTexZ;
	wall.BottomTexZ = bottomTexZ;
	wall.Wallpart = side_t::mid;
	wall.Colormap = GetColorTable(frontsector->Colormap, wall.Side->GetSpecialColor(wall.Wallpart, side_t::walltop, frontsector));
	if (fakeFloor->flags & FF_UPPERTEXTURE)
		wall.Texture = GetTexture(line->linedef, line->sidedef, side_t::top);
	else if (fakeFloor->flags & FF_LOWERTEXTURE)
		wall.Texture = GetTexture(line->linedef, line->sidedef, side_t::bottom);
	else
		wall.Texture = GetTexture(wall.Line, wall.Side, side_t::mid);

	if (frontsector->e->XFloor.lightlist.Size())
	{
		lightlist_t *light = P_GetPlaneLight(frontsector, fakeFloor->top.plane, true);
		wall.Colormap = GetColorTable(light->extra_colormap, wall.Side->GetSpecialColor(wall.Wallpart, side_t::walltop, frontsector));
		wall.SectorLightLevel = *light->p_lightlevel;
	}
	else
	{

		wall.SectorLightLevel = frontsector->lightlevel;
	}

	if (!wall.Masked)
		wall.Render(thread);
	else
		translucentWallsOutput.push_back(thread->FrameMemory->NewObject<PolyTranslucentWall>(wall));
}

void RenderPolyWall::SetCoords(const DVector2 &v1, const DVector2 &v2, double ceil1, double floor1, double ceil2, double floor2)
{
	this->v1 = v1;
	this->v2 = v2;
	this->ceil1 = ceil1;
	this->floor1 = floor1;
	this->ceil2 = ceil2;
	this->floor2 = floor2;
}

void RenderPolyWall::Render(PolyRenderThread *thread)
{
	bool foggy = false;
	if (!Texture && !Polyportal && !FogBoundary)
		return;

	TriVertex *vertices = thread->FrameMemory->AllocMemory<TriVertex>(4);

	vertices[0].x = (float)v1.X;
	vertices[0].y = (float)v1.Y;
	vertices[0].z = (float)ceil1;
	vertices[0].w = 1.0f;

	vertices[1].x = (float)v2.X;
	vertices[1].y = (float)v2.Y;
	vertices[1].z = (float)ceil2;
	vertices[1].w = 1.0f;

	vertices[2].x = (float)v2.X;
	vertices[2].y = (float)v2.Y;
	vertices[2].z = (float)floor2;
	vertices[2].w = 1.0f;

	vertices[3].x = (float)v1.X;
	vertices[3].y = (float)v1.Y;
	vertices[3].z = (float)floor1;
	vertices[3].w = 1.0f;

	if (Texture)
	{
		PolyWallTextureCoordsU texcoordsU(Texture, LineSeg, LineSegLine, Side, Wallpart);
		PolyWallTextureCoordsV texcoordsVLeft(Texture, Line, Side, Wallpart, ceil1, floor1, UnpeggedCeil1, TopTexZ, BottomTexZ);
		PolyWallTextureCoordsV texcoordsVRght(Texture, Line, Side, Wallpart, ceil2, floor2, UnpeggedCeil2, TopTexZ, BottomTexZ);
		vertices[0].u = (float)texcoordsU.u1;
		vertices[0].v = (float)texcoordsVLeft.v1;
		vertices[1].u = (float)texcoordsU.u2;
		vertices[1].v = (float)texcoordsVRght.v1;
		vertices[2].u = (float)texcoordsU.u2;
		vertices[2].v = (float)texcoordsVRght.v2;
		vertices[3].u = (float)texcoordsU.u1;
		vertices[3].v = (float)texcoordsVLeft.v2;
	}
	else
	{
		for (int i = 0; i < 4; i++)
		{
			vertices[i].u = 0.0f;
			vertices[i].v = 0.0f;
		}
	}

	// Masked walls clamp to the 0-1 range (no texture repeat)
	if (Masked)
	{
		bool wrap = (Line->flags & ML_WRAP_MIDTEX) || (Side->Flags & WALLF_WRAP_MIDTEX);
		if (!wrap)
		{
			ClampHeight(vertices[0], vertices[3]);
			ClampHeight(vertices[1], vertices[2]);
		}
	}

	PolyDrawArgs args;
	args.SetLight(Colormap, GetLightLevel(), PolyRenderer::Instance()->Light.WallGlobVis(foggy), false);
	if (Texture && !Polyportal)
		args.SetTexture(Texture, DefaultRenderStyle());

	SetDynLights(thread, args);

	if (FogBoundary)
	{
		args.SetStencilTestValue(StencilValue + 1);
		args.SetStyle(TriBlendMode::FogBoundary);
		args.SetColor(0xffffffff, 254);
		args.SetDepthTest(true);
		args.SetWriteDepth(true);
		args.SetWriteStencil(false);
		PolyTriangleDrawer::DrawArray(thread->DrawQueue, args, vertices, 4, PolyDrawMode::TriangleFan);
		if (!Texture)
			return;
	}

	if (Polyportal)
	{
		args.SetStencilTestValue(StencilValue);
		args.SetWriteStencil(true, Polyportal->StencilValue);
		args.SetWriteColor(false);
		args.SetWriteDepth(false);
		PolyTriangleDrawer::DrawArray(thread->DrawQueue, args, vertices, 4, PolyDrawMode::TriangleFan);
		Polyportal->Shape.push_back({ vertices, 4 });
	}
	else if (!Masked)
	{
		args.SetStencilTestValue(StencilValue);
		args.SetWriteStencil(true, StencilValue + 1);
		args.SetStyle(TriBlendMode::Opaque);
		DrawStripes(thread, args, vertices);
	}
	else
	{
		double a = MIN(Alpha, 1.0);
		if (Additive)
			args.SetStyle(TriBlendMode::Add, a);
		else if (a < 1.0)
			args.SetStyle(TriBlendMode::Translucent, a);
		else
			args.SetStyle(TriBlendMode::Normal);

		args.SetStencilTestValue(StencilValue + 1);
		args.SetDepthTest(true);
		args.SetWriteDepth(true);
		args.SetWriteStencil(false);
		DrawStripes(thread, args, vertices);
	}

	RenderPolyDecal::RenderWallDecals(thread, LineSeg, StencilValue + 1);
}

void RenderPolyWall::SetDynLights(PolyRenderThread *thread, PolyDrawArgs &args)
{
	if (!r_dynlights)
	{
		args.SetLights(nullptr, 0);
		return;
	}

	FLightNode *light_list = (LineSeg && LineSeg->sidedef) ? LineSeg->sidedef->lighthead : nullptr;

	auto cameraLight = PolyCameraLight::Instance();
	if ((cameraLight->FixedLightLevel() >= 0) || (cameraLight->FixedColormap() != nullptr))
	{
		args.SetLights(nullptr, 0); // [SP] Don't draw dynlights if invul/lightamp active
		return;
	}

	// Calculate max lights that can touch the wall so we can allocate memory for the list
	int max_lights = 0;
	FLightNode *cur_node = light_list;
	while (cur_node)
	{
		if (cur_node->lightsource->IsActive())
			max_lights++;
		cur_node = cur_node->nextLight;
	}

	if (max_lights == 0)
	{
		args.SetLights(nullptr, 0);
		return;
	}

	int dc_num_lights = 0;
	PolyLight *dc_lights = thread->FrameMemory->AllocMemory<PolyLight>(max_lights);

	// Setup lights
	cur_node = light_list;
	while (cur_node)
	{
		if (cur_node->lightsource->IsActive())
		{
			bool is_point_light = cur_node->lightsource->IsAttenuated();

			// To do: cull lights not touching wall

			uint32_t red = cur_node->lightsource->GetRed();
			uint32_t green = cur_node->lightsource->GetGreen();
			uint32_t blue = cur_node->lightsource->GetBlue();

			auto &light = dc_lights[dc_num_lights++];
			light.x = (float)cur_node->lightsource->X();
			light.y = (float)cur_node->lightsource->Y();
			light.z = (float)cur_node->lightsource->Z();
			light.radius = 256.0f / cur_node->lightsource->GetRadius();
			light.color = (red << 16) | (green << 8) | blue;
			if (is_point_light)
				light.radius = -light.radius;
		}

		cur_node = cur_node->nextLight;
	}

	args.SetLights(dc_lights, dc_num_lights);

	// Face normal:
	float dx = (float)(v2.X - v1.X);
	float dy = (float)(v2.Y - v1.Y);
	float nx = dy;
	float ny = -dx;
	float lensqr = nx * nx + ny * ny;
	float rcplen = 1.0f / sqrt(lensqr);
	nx *= rcplen;
	ny *= rcplen;
	args.SetNormal({ nx, ny, 0.0f });
}

void RenderPolyWall::DrawStripes(PolyRenderThread *thread, PolyDrawArgs &args, TriVertex *vertices)
{
	const auto &lightlist = Line->frontsector->e->XFloor.lightlist;
	if (lightlist.Size() > 0)
	{
		PolyClipPlane topPlane;

		for (unsigned int i = 0; i < lightlist.Size(); i++)
		{
			lightlist_t *lit = &lightlist[i];

			DVector3 normal = lit->plane.Normal();
			double d = lit->plane.fD();
			if (normal.Z < 0.0)
			{
				normal = -normal;
				d = -d;
			}

			PolyClipPlane bottomPlane = { (float)normal.X, (float)normal.Y, (float)normal.Z, (float)d };

			args.SetClipPlane(1, topPlane);
			args.SetClipPlane(2, bottomPlane);
			PolyTriangleDrawer::DrawArray(thread->DrawQueue, args, vertices, 4, PolyDrawMode::TriangleFan);

			FDynamicColormap *basecolormap = GetColorTable(lit->extra_colormap, Line->frontsector->SpecialColors[sector_t::walltop]);

			bool foggy = false;
			int lightlevel;
			PolyCameraLight *cameraLight = PolyCameraLight::Instance();
			if (cameraLight->FixedLightLevel() >= 0 || cameraLight->FixedColormap())
			{
				lightlevel = 255;
			}
			else
			{
				int actualextralight = foggy ? 0 : PolyRenderer::Instance()->Viewpoint.extralight << 4;
				lightlevel = clamp(Side->GetLightLevel(foggy, *lit->p_lightlevel) + actualextralight, 0, 255);
			}
			args.SetLight(basecolormap, lightlevel, PolyRenderer::Instance()->Light.WallGlobVis(foggy), false);

			topPlane = { (float)-normal.X, (float)-normal.Y, (float)-normal.Z, (float)-d };
		}

		args.SetClipPlane(1, topPlane);
		args.SetClipPlane(2, PolyClipPlane());
		PolyTriangleDrawer::DrawArray(thread->DrawQueue, args, vertices, 4, PolyDrawMode::TriangleFan);
	}
	else
	{
		PolyTriangleDrawer::DrawArray(thread->DrawQueue, args, vertices, 4, PolyDrawMode::TriangleFan);
	}
}

void RenderPolyWall::ClampHeight(TriVertex &v1, TriVertex &v2)
{
	float top = v1.z;
	float bottom = v2.z;
	float texv1 = v1.v;
	float texv2 = v2.v;
	float delta = (texv2 - texv1);

	float t1 = texv1 < 0.0f ? -texv1 / delta : 0.0f;
	float t2 = texv2 > 1.0f ? (1.0f - texv1) / delta : 1.0f;
	float inv_t1 = 1.0f - t1;
	float inv_t2 = 1.0f - t2;

	v1.z = top * inv_t1 + bottom * t1;
	v1.v = texv1 * inv_t1 + texv2 * t1;

	v2.z = top * inv_t2 + bottom * t2;
	v2.v = texv1 * inv_t2 + texv2 * t2;
}

FSoftwareTexture *RenderPolyWall::GetTexture(const line_t *line, const side_t *side, side_t::ETexpart texpart)
{
	FTexture *tex = TexMan.GetPalettedTexture(side->GetTexture(texpart), true);
	if (tex == nullptr || !tex->isValid())
	{
		// Mapping error. Doom floodfills this with a plane.
		// This code doesn't do that, but at least it uses the "right" texture..

		if (line && line->backsector && line->sidedef[0] == side)
		{
			if (texpart == side_t::top)
				tex = TexMan.GetPalettedTexture(line->backsector->GetTexture(sector_t::ceiling), true);
			else if (texpart == side_t::bottom)
				tex = TexMan.GetPalettedTexture(line->backsector->GetTexture(sector_t::floor), true);
		}
		if (line && line->backsector && line->sidedef[1] == side)
		{
			if (texpart == side_t::top)
				tex = TexMan.GetPalettedTexture(line->frontsector->GetTexture(sector_t::ceiling), true);
			else if (texpart == side_t::bottom)
				tex = TexMan.GetPalettedTexture(line->frontsector->GetTexture(sector_t::floor), true);
		}

		if (tex == nullptr || !tex->isValid())
			return nullptr;
	}
	return tex? tex->GetSoftwareTexture() : nullptr;
}

int RenderPolyWall::GetLightLevel()
{
	PolyCameraLight *cameraLight = PolyCameraLight::Instance();
	if (cameraLight->FixedLightLevel() >= 0 || cameraLight->FixedColormap())
	{
		return 255;
	}
	else
	{
		bool foggy = false;
		int actualextralight = foggy ? 0 : PolyRenderer::Instance()->Viewpoint.extralight << 4;
		return clamp(Side->GetLightLevel(foggy, SectorLightLevel) + actualextralight, 0, 255);
	}
}

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

PolyWallTextureCoordsU::PolyWallTextureCoordsU(FSoftwareTexture *tex, const seg_t *lineseg, const line_t *line, const side_t *side, side_t::ETexpart wallpart)
{
	// Calculate the U texture coordinate for the line
	double lineu1 = side->GetTextureXOffset(wallpart);
	double lineu2 = side->GetTextureXOffset(wallpart) + line->sidedef[0]->TexelLength * side->GetTextureXScale(wallpart);
	lineu1 *= tex->GetScale().X / tex->GetWidth();
	lineu2 *= tex->GetScale().X / tex->GetWidth();

	// Calculate where we are on the lineseg
	double 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;
	}

	// Check if lineseg is the backside of the line
	if (t2 < t1)
	{
		std::swap(lineu1, lineu2);
	}

	// Calculate texture coordinates for the lineseg
	u1 = (1.0 - t1) * lineu1 + t1 * lineu2;
	u2 = (1.0 - t2) * lineu1 + t2 * lineu2;
}

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

PolyWallTextureCoordsV::PolyWallTextureCoordsV(FSoftwareTexture *tex, const line_t *line, const side_t *side, side_t::ETexpart wallpart, double topz, double bottomz, double unpeggedceil, double topTexZ, double bottomTexZ)
{
	double yoffset = side->GetTextureYOffset(wallpart);
	if (tex->useWorldPanning(line->GetLevel()))
		yoffset *= side->GetTextureYScale(wallpart) * tex->GetScale().Y;

	switch (wallpart)
	{
	default:
	case side_t::mid:
		CalcVMidPart(tex, line, side, topTexZ, bottomTexZ, yoffset);
		break;
	case side_t::top:
		CalcVTopPart(tex, line, side, topTexZ, bottomTexZ, yoffset);
		break;
	case side_t::bottom:
		CalcVBottomPart(tex, line, side, topTexZ, bottomTexZ, unpeggedceil, yoffset);
		break;
	}

	v1 *= tex->GetScale().Y / tex->GetHeight();
	v2 *= tex->GetScale().Y / tex->GetHeight();

	double texZHeight = (bottomTexZ - topTexZ);
	if (texZHeight > 0.0f || texZHeight < -0.0f)
	{
		double t1 = (topz - topTexZ) / texZHeight;
		double t2 = (bottomz - topTexZ) / texZHeight;
		double vorig1 = v1;
		double vorig2 = v2;
		v1 = vorig1 * (1.0f - t1) + vorig2 * t1;
		v2 = vorig1 * (1.0f - t2) + vorig2 * t2;
	}
}

void PolyWallTextureCoordsV::CalcVTopPart(FSoftwareTexture *tex, const line_t *line, const side_t *side, double topz, double bottomz, double yoffset)
{
	bool pegged = (line->flags & ML_DONTPEGTOP) == 0;
	if (pegged) // bottom to top
	{
		double texHeight = tex->GetHeight() / tex->GetScale().Y;
		v1 = (topz - bottomz) * side->GetTextureYScale(side_t::top) - yoffset;
		v2 = -yoffset;
		v1 = texHeight - v1;
		v2 = texHeight - v2;
	}
	else // top to bottom
	{
		v1 = yoffset;
		v2 = (topz - bottomz) * side->GetTextureYScale(side_t::top) + yoffset;
	}
}

void PolyWallTextureCoordsV::CalcVMidPart(FSoftwareTexture *tex, const line_t *line, const side_t *side, double topz, double bottomz, double yoffset)
{
	bool pegged = (line->flags & ML_DONTPEGBOTTOM) == 0;
	if (pegged) // top to bottom
	{
		v1 = yoffset;
		v2 = (topz - bottomz) * side->GetTextureYScale(side_t::mid) + yoffset;
	}
	else // bottom to top
	{
		double texHeight = tex->GetHeight() / tex->GetScale().Y;
		v1 = yoffset - (topz - bottomz) * side->GetTextureYScale(side_t::mid);
		v2 = yoffset;
		v1 = texHeight + v1;
		v2 = texHeight + v2;
	}
}

void PolyWallTextureCoordsV::CalcVBottomPart(FSoftwareTexture *tex, const line_t *line, const side_t *side, double topz, double bottomz, double unpeggedceil, double yoffset)
{
	bool pegged = (line->flags & ML_DONTPEGBOTTOM) == 0;
	if (pegged) // top to bottom
	{
		v1 = yoffset;
		v2 = yoffset + (topz - bottomz) * side->GetTextureYScale(side_t::bottom);
	}
	else
	{
		v1 = yoffset + (unpeggedceil - topz) * side->GetTextureYScale(side_t::bottom);
		v2 = yoffset + (unpeggedceil - bottomz) * side->GetTextureYScale(side_t::bottom);
	}
}
