//**************************************************************************
//**
//**	##   ##    ##    ##   ##   ####     ####   ###     ###
//**	##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
//**	 ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
//**	 ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
//**	  ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
//**	   #    ##    ##    #      ####     ####   ##       ##
//**
//**	$Id: r_surf.cpp 4340 2010-12-15 21:48:23Z dj_jl $
//**
//**	Copyright (C) 1999-2006 Jānis Legzdiņš
//**
//**	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 2
//**  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.
//**
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include "gamedefs.h"
#include "r_local.h"

// MACROS ------------------------------------------------------------------

#define MAXSPLITVERTS		128
#define ON_EPSILON			0.1
#define subdivide_size		240

#define MAXWVERTS	8
#define WSURFSIZE	(sizeof(surface_t) + sizeof(TVec) * (MAXWVERTS - 1))

//	This is used to compare floats like ints which is faster
#define FASI(var)	(*(int*)&var)

// TYPES -------------------------------------------------------------------

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

extern int			light_mem;

// PUBLIC DATA DEFINITIONS -------------------------------------------------

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static subsector_t*	r_sub;
static sec_plane_t*	r_floor;
static sec_plane_t*	r_ceiling;

static segpart_t*	pspart;

// CODE --------------------------------------------------------------------

//**************************************************************************
//
//	Scaling
//
//**************************************************************************

//==========================================================================
//
//	TextureSScale
//
//==========================================================================

inline float TextureSScale(VTexture* pic)
{
	return pic->SScale;
}

//==========================================================================
//
//	TextureTScale
//
//==========================================================================

inline float TextureTScale(VTexture* pic)
{
	return pic->TScale;
}

//==========================================================================
//
//	TextureOffsetSScale
//
//==========================================================================

inline float TextureOffsetSScale(VTexture* pic)
{
	if (pic->bWorldPanning)
		return pic->SScale;
	return 1.0;
}

//==========================================================================
//
//	TextureOffsetTScale
//
//==========================================================================

inline float TextureOffsetTScale(VTexture* pic)
{
	if (pic->bWorldPanning)
		return pic->TScale;
	return 1.0;
}

//==========================================================================
//
//	IsSky
//
//==========================================================================

inline bool IsSky(sec_plane_t* SPlane)
{
	return SPlane->pic == skyflatnum || (SPlane->SkyBox &&
		SPlane->SkyBox->eventSkyBoxGetAlways());
}

//**************************************************************************
//**
//**	Sector surfaces
//**
//**************************************************************************

//==========================================================================
//
//	VRenderLevelShared::SetupSky
//
//==========================================================================

void VRenderLevelShared::SetupSky()
{
	guard(VRenderLevelShared::SetupSky);
	skyheight = -99999.0;
	for (int i = 0; i < Level->NumSectors; i++)
	{
		if (Level->Sectors[i].ceiling.pic == skyflatnum &&
			Level->Sectors[i].ceiling.maxz > skyheight)
		{
			skyheight = Level->Sectors[i].ceiling.maxz;
		}
	}
	//	Make it a bit higher to avoid clipping of the sprites.
	skyheight += 8 * 1024;
	memset(&sky_plane, 0, sizeof(sky_plane));
	sky_plane.Set(TVec(0, 0, -1), -skyheight);
	sky_plane.pic = skyflatnum;
	sky_plane.Alpha = 1.1;
	sky_plane.LightSourceSector = -1;
	sky_plane.MirrorAlpha = 1.0;
	sky_plane.XScale = 1.0;
	sky_plane.YScale = 1.0;
	unguard;
}

//==========================================================================
//
//	VRenderLevel::InitSurfs
//
//==========================================================================

void VRenderLevel::InitSurfs(surface_t* ASurfs, texinfo_t *texinfo,
	TPlane *plane, subsector_t* sub)
{
	guard(VRenderLevel::InitSurfs);
	surface_t* surfs = ASurfs;
	int i;
	float dot;
	float mins;
	float maxs;
	int bmins;
	int bmaxs;

	while (surfs)
	{
		if (plane)
		{
			surfs->texinfo = texinfo;
			surfs->plane = plane;
		}

		mins = 99999.0;
		maxs = -99999.0;
		for (i = 0; i < surfs->count; i++)
		{
			dot = DotProduct(surfs->verts[i], texinfo->saxis) + texinfo->soffs;
			if (dot < mins)
				mins = dot;
			if (dot > maxs)
				maxs = dot;
		}
		bmins = (int)floor(mins / 16);
		bmaxs = (int)ceil(maxs / 16);
		surfs->texturemins[0] = bmins * 16;
		surfs->extents[0] = (bmaxs - bmins) * 16;
		if (surfs->extents[0] > 256)
		{
			Sys_Error("Bad extents");
		}

		mins = 99999.0;
		maxs = -99999.0;
		for (i = 0; i < surfs->count; i++)
		{
			dot = DotProduct(surfs->verts[i], texinfo->taxis) + texinfo->toffs;
			if (dot < mins)
				mins = dot;
			if (dot > maxs)
				maxs = dot;
		}
		bmins = (int)floor(mins / 16);
		bmaxs = (int)ceil(maxs / 16);
		surfs->texturemins[1] = bmins * 16;
		surfs->extents[1] = (bmaxs - bmins) * 16;
		if (surfs->extents[1] > 256)
		{
			Sys_Error("Bad extents");
		}

		LightFace(surfs, sub);

		surfs = surfs->next;
	}
	unguard;
}

//==========================================================================
//
//	VAdvancedRenderLevel::InitSurfs
//
//==========================================================================

void VAdvancedRenderLevel::InitSurfs(surface_t* surfs, texinfo_t *texinfo,
	TPlane *plane, subsector_t*)
{
	guard(VAdvancedRenderLevel::InitSurfs);
	//	It's always one surface.
	if (surfs && plane)
	{
		surfs->texinfo = texinfo;
		surfs->plane = plane;
	}
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::FlushSurfCaches
//
//==========================================================================

void VRenderLevelShared::FlushSurfCaches(surface_t* InSurfs)
{
	guard(VRenderLevelShared::FlushSurfCaches);
	surface_t* surfs = InSurfs;
	while (surfs)
	{
		if (surfs->CacheSurf)
		{
			FreeSurfCache(surfs->CacheSurf);
		}
		surfs = surfs->next;
	}
	unguard;
}

//==========================================================================
//
//	VRenderLevel::SubdivideFace
//
//==========================================================================

surface_t* VRenderLevel::SubdivideFace(surface_t* InF, const TVec &axis,
	const TVec *nextaxis)
{
	guard(VRenderLevel::SubdivideFace);
	surface_t* f = InF;
	int i;
	float dot;
	float mins = 99999.0;
	float maxs = -99999.0;

	for (i = 0; i < f->count; i++)
	{
		dot = DotProduct(f->verts[i], axis);
		if (dot < mins)
			mins = dot;
		if (dot > maxs)
			maxs = dot;
	}

	if (maxs - mins <= subdivide_size)
	{
		if (nextaxis)
		{
			f = SubdivideFace(f, *nextaxis, NULL);
		}
		return f;
	}

	c_subdivides++;

	if (f->count > MAXSPLITVERTS)
	{
		Host_Error("f->count > MAXSPLITVERTS\n");
	}

	TPlane plane;

	plane.normal = axis;
	dot = Length(plane.normal);
	plane.normal = Normalise(plane.normal);
	plane.dist = (mins + subdivide_size - 16) / dot;

	float dots[MAXSPLITVERTS + 1];
	int sides[MAXSPLITVERTS + 1];

	for (i = 0; i < f->count; i++)
	{
		dot = DotProduct(f->verts[i], plane.normal) - plane.dist;
		dots[i] = dot;
		if (dot < -ON_EPSILON)
			sides[i] = -1;
		else if (dot > ON_EPSILON)
			sides[i] = 1;
		else
			sides[i] = 0;
	}
	dots[f->count] = dots[0];
	sides[f->count] = sides[0];

	TVec verts1[MAXSPLITVERTS];
	TVec verts2[MAXSPLITVERTS];
	int count1 = 0;
	int count2 = 0;

	for (i = 0; i < f->count; i++)
	{
		if (sides[i] == 0)
		{
			verts1[count1++] = f->verts[i];
			verts2[count2++] = f->verts[i];
			continue;
		}
		if (sides[i] == 1)
		{
			verts1[count1++] = f->verts[i];
		}
		else
		{
			verts2[count2++] = f->verts[i];
		}
		if (sides[i + 1] == 0 || sides[i] == sides[i + 1])
		{
			continue;
		}

		// generate a split point
		TVec mid;
		TVec &p1 = f->verts[i];
		TVec &p2 = f->verts[(i + 1) % f->count];
		
		dot = dots[i] / (dots[i] - dots[i + 1]);
		for (int j = 0; j < 3; j++)
		{
			// avoid round off error when possible
			if (plane.normal[j] == 1)
				mid[j] = plane.dist;
			else if (plane.normal[j] == -1)
				mid[j] = -plane.dist;
			else
				mid[j] = p1[j] + dot * (p2[j] - p1[j]);
		}

		verts1[count1++] = mid;
		verts2[count2++] = mid;
	}

	surface_t *next = f->next;
	Z_Free(f);

	surface_t *back = (surface_t*)Z_Calloc(sizeof(surface_t) +
		(count2 - 1) * sizeof(TVec));
	back->count = count2;
	memcpy(back->verts, verts2, count2 * sizeof(TVec));

	surface_t *front = (surface_t*)Z_Calloc(sizeof(surface_t) +
		(count1 - 1) * sizeof(TVec));
	front->count = count1;
	memcpy(front->verts, verts1, count1 * sizeof(TVec));

	front->next = next;
	back->next = SubdivideFace(front, axis, nextaxis);
	if (nextaxis)
	{
		back = SubdivideFace(back, *nextaxis, NULL);
	}
	return back;
	unguard;
}

//==========================================================================
//
//	VAdvancedRenderLevel::SubdivideFace
//
//==========================================================================

surface_t* VAdvancedRenderLevel::SubdivideFace(surface_t* f, const TVec&,
	const TVec*)
{
	//	Advanced renderer can draw whole surface.
	return f;
}

//==========================================================================
//
//	VRenderLevelShared::CreateSecSurface
//
//==========================================================================

sec_surface_t* VRenderLevelShared::CreateSecSurface(subsector_t* sub,
	sec_plane_t* InSplane)
{
	guard(VRenderLevelShared::CreateSecSurface);
	sec_plane_t* splane = InSplane;
	sec_surface_t	*ssurf;
	surface_t		*surf;

	ssurf = new sec_surface_t;
	memset(ssurf, 0, sizeof(sec_surface_t));
	surf = (surface_t*)Z_Calloc(sizeof(surface_t) +
		(sub->numlines - 1) * sizeof(TVec));

	if (splane->pic == skyflatnum && splane->normal.z < 0.0)
	{
		splane = &sky_plane;
	}
	ssurf->secplane = splane;
	ssurf->dist = splane->dist;

	VTexture* Tex = GTextureManager(splane->pic);
	if (fabs(splane->normal.z) > 0.1)
	{
		ssurf->texinfo.saxis = TVec(mcos(splane->BaseAngle - splane->Angle),
			msin(splane->BaseAngle - splane->Angle), 0) * TextureSScale(Tex) *
			splane->XScale;
		ssurf->texinfo.taxis = TVec(msin(splane->BaseAngle - splane->Angle),
			-mcos(splane->BaseAngle - splane->Angle), 0) * TextureTScale(Tex) *
			splane->YScale;
	}
	else
	{
		ssurf->texinfo.taxis = TVec(0, 0, -1) * TextureTScale(Tex) *
			splane->YScale;
		ssurf->texinfo.saxis = Normalise(CrossProduct(splane->normal,
			ssurf->texinfo.taxis)) * TextureSScale(Tex) * splane->XScale;
	}
	ssurf->texinfo.soffs = splane->xoffs;
	ssurf->texinfo.toffs = splane->yoffs + splane->BaseYOffs;
	ssurf->texinfo.Tex = Tex;
	ssurf->texinfo.Alpha = splane->Alpha < 1.0 ? splane->Alpha : 1.1;
	ssurf->texinfo.Additive = !!(splane->flags & SPF_ADDITIVE);
	ssurf->texinfo.ColourMap = 0;
	ssurf->XScale = splane->XScale;
	ssurf->YScale = splane->YScale;
	ssurf->Angle = splane->BaseAngle - splane->Angle;

	surf->count = sub->numlines;
	seg_t *line = &Level->Segs[sub->firstline];
	int vlindex = (splane->normal.z < 0);
	for (int i = 0; i < surf->count; i++)
	{
		TVec &v = *line[vlindex ? surf->count - i - 1 : i].v1;
		TVec &dst = surf->verts[i];
		dst = v;
		dst.z = splane->GetPointZ(dst);
	}

	if (splane->pic == skyflatnum)
	{
		//	Don't subdivide sky
		ssurf->surfs = surf;
		surf->texinfo = &ssurf->texinfo;
		surf->plane = splane;
	}
	else
	{
		ssurf->surfs = SubdivideFace(surf, ssurf->texinfo.saxis,
			&ssurf->texinfo.taxis);
		InitSurfs(ssurf->surfs, &ssurf->texinfo, splane, sub);
	}

	return ssurf;
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::UpdateSecSurface
//
//==========================================================================

void VRenderLevelShared::UpdateSecSurface(sec_surface_t *ssurf,
	sec_plane_t* RealPlane, subsector_t* sub)
{
	guard(VRenderLevelShared::UpdateSecSurface);
	sec_plane_t		*plane = ssurf->secplane;

	if (!plane->pic)
	{
		return;
	}

	if (plane != RealPlane)
	{
		//	Check for sky changes.
		if (plane->pic == skyflatnum && RealPlane->pic != skyflatnum)
		{
			ssurf->secplane = RealPlane;
			plane = RealPlane;
			if (!ssurf->surfs->extents[0])
			{
				ssurf->surfs = SubdivideFace(ssurf->surfs,
					ssurf->texinfo.saxis, &ssurf->texinfo.taxis);
				InitSurfs(ssurf->surfs, &ssurf->texinfo, plane, sub);
			}
		}
		else if (plane->pic != skyflatnum && RealPlane->pic == skyflatnum)
		{
			ssurf->secplane = &sky_plane;
			plane = &sky_plane;
		}
	}

	ssurf->texinfo.ColourMap = ColourMap;
	if (ssurf->texinfo.Tex != GTextureManager(plane->pic))
	{
		ssurf->texinfo.Tex = GTextureManager(plane->pic);
	}
	if (FASI(ssurf->dist) != FASI(plane->dist))
	{
		ssurf->dist = plane->dist;
		for (surface_t *surf = ssurf->surfs; surf; surf = surf->next)
		{
			for (int i = 0; i < surf->count; i++)
			{
				surf->verts[i].z = plane->GetPointZ(surf->verts[i]);
			}
		}
		if (plane->pic != skyflatnum)
		{
			FlushSurfCaches(ssurf->surfs);
			InitSurfs(ssurf->surfs, &ssurf->texinfo, NULL, sub);
		}
	}
	if (FASI(ssurf->XScale) != FASI(plane->XScale) ||
		FASI(ssurf->YScale) != FASI(plane->YScale) ||
		ssurf->Angle != plane->BaseAngle - plane->Angle)
	{
		if (fabs(plane->normal.z) > 0.1)
		{
			ssurf->texinfo.saxis = TVec(mcos(plane->BaseAngle - plane->Angle),
				msin(plane->BaseAngle - plane->Angle), 0) *
				TextureSScale(ssurf->texinfo.Tex) * plane->XScale;
			ssurf->texinfo.taxis = TVec(msin(plane->BaseAngle - plane->Angle),
				-mcos(plane->BaseAngle - plane->Angle), 0) *
				TextureTScale(ssurf->texinfo.Tex) * plane->YScale;
		}
		else
		{
			ssurf->texinfo.taxis = TVec(0, 0, -1) *
				TextureTScale(ssurf->texinfo.Tex) * plane->YScale;
			ssurf->texinfo.saxis = Normalise(CrossProduct(plane->normal,
				ssurf->texinfo.taxis)) * TextureSScale(ssurf->texinfo.Tex) *
				plane->XScale;
		}
		ssurf->texinfo.soffs = plane->xoffs;
		ssurf->texinfo.toffs = plane->yoffs + plane->BaseYOffs;
		ssurf->XScale = plane->XScale;
		ssurf->YScale = plane->YScale;
		ssurf->Angle = plane->BaseAngle - plane->Angle;
		if (plane->pic != skyflatnum)
		{
			FreeSurfaces(ssurf->surfs);
			surface_t* surf = (surface_t*)Z_Calloc(sizeof(surface_t) +
				(sub->numlines - 1) * sizeof(TVec));
			surf->count = sub->numlines;
			seg_t* line = &Level->Segs[sub->firstline];
			int vlindex = (plane->normal.z < 0);
			for (int i = 0; i < surf->count; i++)
			{
				TVec &v = *line[vlindex ? surf->count - i - 1 : i].v1;
				TVec &dst = surf->verts[i];
				dst = v;
				dst.z = plane->GetPointZ(dst);
			}
			ssurf->surfs = SubdivideFace(surf, ssurf->texinfo.saxis,
				&ssurf->texinfo.taxis);
			InitSurfs(ssurf->surfs, &ssurf->texinfo, plane, sub);
		}
	}
	else if (FASI(ssurf->texinfo.soffs) != FASI(plane->xoffs) ||
		ssurf->texinfo.toffs != plane->yoffs + plane->BaseYOffs)
	{
		ssurf->texinfo.soffs = plane->xoffs;
		ssurf->texinfo.toffs = plane->yoffs + plane->BaseYOffs;
		if (plane->pic != skyflatnum)
		{
			FlushSurfCaches(ssurf->surfs);
			InitSurfs(ssurf->surfs, &ssurf->texinfo, NULL, sub);
		}
	}
	unguard;
}

//**************************************************************************
//**
//**	Seg surfaces
//**
//**************************************************************************

//==========================================================================
//
//	VRenderLevelShared::NewWSurf
//
//==========================================================================

surface_t* VRenderLevelShared::NewWSurf()
{
	guard(VRenderLevelShared::NewWSurf);
	if (!free_wsurfs)
	{
		//	Allocate some more surfs
		byte* tmp = (byte*)Z_Malloc(WSURFSIZE * 32 + sizeof(void*));
		*(void**)tmp = AllocatedWSurfBlocks;
		AllocatedWSurfBlocks = tmp;
		tmp += sizeof(void*);
		for (int i = 0; i < 32; i++)
		{
			((surface_t*)tmp)->next = free_wsurfs;
			free_wsurfs = (surface_t*)tmp;
			tmp += WSURFSIZE;
		}
	}
	surface_t *surf = free_wsurfs;
	free_wsurfs = surf->next;

	memset(surf, 0, WSURFSIZE);

	return surf;
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::FreeWSurfs
//
//==========================================================================

void VRenderLevelShared::FreeWSurfs(surface_t* InSurfs)
{
	guard(VRenderLevelShared::FreeWSurfs);
	surface_t* surfs = InSurfs;
	FlushSurfCaches(surfs);
	while (surfs)
	{
		if (surfs->lightmap)
		{
			Z_Free(surfs->lightmap);
		}
		if (surfs->lightmap_rgb)
		{
			Z_Free(surfs->lightmap_rgb);
		}
		surface_t *next = surfs->next;
		surfs->next = free_wsurfs;
		free_wsurfs = surfs;
		surfs = next;
	}
	unguard;
}

//==========================================================================
//
//	VRenderLevel::SubdivideSeg
//
//==========================================================================

surface_t* VRenderLevel::SubdivideSeg(surface_t* InSurf,
	const TVec &axis, const TVec *nextaxis)
{
	guard(VRenderLevel::SubdivideSeg);
	surface_t* surf = InSurf;
	int i;
	float dot;
	float mins = 99999.0;
	float maxs = -99999.0;

	for (i = 0; i < surf->count; i++)
	{
		dot = DotProduct(surf->verts[i], axis);
		if (dot < mins)
			mins = dot;
		if (dot > maxs)
			maxs = dot;
	}

	if (maxs - mins <= subdivide_size)
	{
		if (nextaxis)
		{
			surf = SubdivideSeg(surf, *nextaxis, NULL);
		}
		return surf;
	}

	c_seg_div++;

	TPlane plane;

	plane.normal = axis;
	dot = Length(plane.normal);
	plane.normal = Normalise(plane.normal);
	plane.dist = (mins + subdivide_size - 16) / dot;

	float dots[MAXWVERTS + 1];
	int sides[MAXWVERTS + 1];

	for (i = 0; i < surf->count; i++)
	{
		dot = DotProduct(surf->verts[i], plane.normal) - plane.dist;
		dots[i] = dot;
		if (dot < -ON_EPSILON)
			sides[i] = -1;
		else if (dot > ON_EPSILON)
			sides[i] = 1;
		else
			sides[i] = 0;
	}
	dots[surf->count] = dots[0];
	sides[surf->count] = sides[0];

	TVec verts1[MAXWVERTS];
	TVec verts2[MAXWVERTS];
	int count1 = 0;
	int count2 = 0;

	for (i = 0; i < surf->count; i++)
	{
		if (sides[i] == 0)
		{
			verts1[count1++] = surf->verts[i];
			verts2[count2++] = surf->verts[i];
			continue;
		}
		if (sides[i] == 1)
		{
			verts1[count1++] = surf->verts[i];
		}
		else
		{
			verts2[count2++] = surf->verts[i];
		}
		if (sides[i + 1] == 0 || sides[i] == sides[i + 1])
		{
			continue;
		}

		// generate a split point
		TVec mid;
		TVec &p1 = surf->verts[i];
		TVec &p2 = surf->verts[(i + 1) % surf->count];
		
		dot = dots[i] / (dots[i] - dots[i + 1]);
		for (int j = 0; j < 3; j++)
		{
			// avoid round off error when possible
			if (plane.normal[j] == 1)
				mid[j] = plane.dist;
			else if (plane.normal[j] == -1)
				mid[j] = -plane.dist;
			else
				mid[j] = p1[j] + dot * (p2[j] - p1[j]);
		}

		verts1[count1++] = mid;
		verts2[count2++] = mid;
	}

	surf->count = count2;
	memcpy(surf->verts, verts2, count2 * sizeof(TVec));

	surface_t *news = NewWSurf();
	news->count = count1;
	memcpy(news->verts, verts1, count1 * sizeof(TVec));

	news->next = surf->next;
	surf->next = SubdivideSeg(news, axis, nextaxis);
	if (nextaxis)
	{
		surf = SubdivideSeg(surf, *nextaxis, NULL);
	}
	return surf;
	unguard;
}

//==========================================================================
//
//	VAdvancedRenderLevel::SubdivideSeg
//
//==========================================================================

surface_t* VAdvancedRenderLevel::SubdivideSeg(surface_t* surf, const TVec&,
	const TVec*)
{
	//	Advanced renderer can draw whole surface.
	return surf;
}

//==========================================================================
//
//	VRenderLevelShared::CreateWSurfs
//
//==========================================================================

surface_t* VRenderLevelShared::CreateWSurfs(TVec* wv, texinfo_t* texinfo,
	seg_t* seg, subsector_t* sub)
{
	guard(VRenderLevelShared::CreateWSurfs);
	if (wv[1].z <= wv[0].z && wv[2].z <= wv[3].z)
	{
		return NULL;
	}

	if (texinfo->Tex->Type == TEXTYPE_Null)
	{
		return NULL;
	}

	surface_t *surf = NewWSurf();
	surf->next = NULL;
	surf->count = 4;
	memcpy(surf->verts, wv, 4 * sizeof(TVec));

	if (texinfo->Tex == GTextureManager[skyflatnum])
	{
		//	Never split sky surfaces
		surf->texinfo = texinfo;
		surf->plane = seg;
		return surf;
	}

	surf = SubdivideSeg(surf, texinfo->saxis, &texinfo->taxis);
	InitSurfs(surf, texinfo, seg, sub);
	return surf;
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::CountSegParts
//
//==========================================================================

int VRenderLevelShared::CountSegParts(seg_t* seg)
{
	guard(VRenderLevelShared::CountSegParts);
	if (!seg->linedef)
	{
		//	Miniseg
		return 0;
	}
	int			count;
	if (!seg->backsector)
	{
		count = 2;
	}
	else
	{
		count = 4;
		for (sec_region_t *reg = seg->backsector->topregion;
			reg->prev; reg = reg->prev)
		{
			count++;
		}
	}
	return count;
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::CreateSegParts
//
//==========================================================================

void VRenderLevelShared::CreateSegParts(drawseg_t* dseg, seg_t *seg)
{
	guard(VRenderLevelShared::CreateSegParts);
	TVec		wv[4];
	segpart_t	*sp;

	dseg->seg = seg;
	dseg->next = seg->drawsegs;
	seg->drawsegs = dseg;

	if (!seg->linedef)
	{
		//	Miniseg
		return;
	}

	side_t *sidedef = seg->sidedef;
	line_t *linedef = seg->linedef;

	TVec segdir = (*seg->v2 - *seg->v1) / seg->length;

	float topz1 = r_ceiling->GetPointZ(*seg->v1);
	float topz2 = r_ceiling->GetPointZ(*seg->v2);
	float botz1 = r_floor->GetPointZ(*seg->v1);
	float botz2 = r_floor->GetPointZ(*seg->v2);

	if (!seg->backsector)
	{
		dseg->mid = pspart++;
		sp = dseg->mid;

		VTexture* MTex = GTextureManager(sidedef->MidTexture);
		sp->texinfo.saxis = segdir * TextureSScale(MTex);
		sp->texinfo.taxis = TVec(0, 0, -1) * TextureTScale(MTex);
		sp->texinfo.soffs = -DotProduct(*seg->v1, sp->texinfo.saxis) +
			seg->offset * TextureSScale(MTex) +
			sidedef->MidTextureOffset * TextureOffsetSScale(MTex);
		sp->texinfo.Tex = MTex;
		sp->texinfo.Alpha = 1.1;
		sp->texinfo.Additive = false;
		sp->texinfo.ColourMap = 0;

		if (linedef->flags & ML_DONTPEGBOTTOM)
		{
			//	bottom of texture at bottom
			sp->texinfo.toffs = r_floor->TexZ + MTex->GetScaledHeight();
		}
		else if (linedef->flags & ML_DONTPEGTOP)
		{
			// top of texture at top of top region
			sp->texinfo.toffs =
				r_sub->sector->topregion->ceiling->TexZ;
		}
		else
		{
			// top of texture at top
			sp->texinfo.toffs = r_ceiling->TexZ;
		}
		sp->texinfo.toffs *= TextureTScale(MTex);
		sp->texinfo.toffs += sidedef->MidRowOffset * TextureOffsetTScale(MTex);

		wv[0].x = wv[1].x = seg->v1->x;
		wv[0].y = wv[1].y = seg->v1->y;
		wv[2].x = wv[3].x = seg->v2->x;
		wv[2].y = wv[3].y = seg->v2->y;

		wv[0].z = botz1;
		wv[1].z = topz1;
		wv[2].z = topz2;
		wv[3].z = botz2;

		sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

		sp->frontTopDist = r_ceiling->dist;
		sp->frontBotDist = r_floor->dist;
		sp->TextureOffset = sidedef->MidTextureOffset;
		sp->RowOffset = sidedef->MidRowOffset;

		//	Sky above line.
		dseg->topsky = pspart++;
		sp = dseg->topsky;
		sp->texinfo.Tex = GTextureManager[skyflatnum];
		sp->texinfo.Alpha = 1.1;
		sp->texinfo.Additive = false;
		sp->texinfo.ColourMap = 0;
		if (IsSky(r_ceiling))
		{
			wv[0].x = wv[1].x = seg->v1->x;
			wv[0].y = wv[1].y = seg->v1->y;
			wv[2].x = wv[3].x = seg->v2->x;
			wv[2].y = wv[3].y = seg->v2->y;

			wv[0].z = topz1;
			wv[1].z = wv[2].z = skyheight;
			wv[3].z = topz2;

			sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

			sp->frontTopDist = r_ceiling->dist;
		}
	}
	else
	{
		// two sided line

		sec_plane_t* back_floor = &seg->backsector->floor;
		sec_plane_t* back_ceiling = &seg->backsector->ceiling;
		if (seg->backsector->fakefloors)
		{
			if (back_floor == &seg->backsector->floor)
			{
				back_floor = &seg->backsector->fakefloors->floorplane;
			}
			if (back_ceiling == &seg->backsector->ceiling)
			{
				back_ceiling = &seg->backsector->fakefloors->ceilplane;
			}
		}

		VTexture* TTex = GTextureManager(sidedef->TopTexture);

		float back_topz1 = back_ceiling->GetPointZ(*seg->v1);
		float back_topz2 = back_ceiling->GetPointZ(*seg->v2);
		float back_botz1 = back_floor->GetPointZ(*seg->v1);
		float back_botz2 = back_floor->GetPointZ(*seg->v2);

		// hack to allow height changes in outdoor areas
		float top_topz1 = topz1;
		float top_topz2 = topz2;
		float top_TexZ = r_ceiling->TexZ;
		if (IsSky(r_ceiling) && IsSky(back_ceiling))
		{
			if (r_ceiling->SkyBox == back_ceiling->SkyBox)
			{
				top_topz1 = back_topz1;
				top_topz2 = back_topz2;
				top_TexZ = back_ceiling->TexZ;
			}
			else
			{
				TTex = GTextureManager[skyflatnum];
			}
		}

		// top wall
		dseg->top = pspart++;
		sp = dseg->top;

		sp->texinfo.saxis = segdir * TextureSScale(TTex);
		sp->texinfo.taxis = TVec(0, 0, -1) * TextureTScale(TTex);
		sp->texinfo.soffs = -DotProduct(*seg->v1, sp->texinfo.saxis) +
			seg->offset * TextureSScale(TTex) +
			sidedef->TopTextureOffset * TextureOffsetSScale(TTex);
		sp->texinfo.Tex = TTex;
		sp->texinfo.Alpha = 1.1;
		sp->texinfo.Additive = false;
		sp->texinfo.ColourMap = 0;

		if (linedef->flags & ML_DONTPEGTOP)
		{
			// top of texture at top
			sp->texinfo.toffs = top_TexZ;
		}
		else
		{
			// bottom of texture
			sp->texinfo.toffs = back_ceiling->TexZ + TTex->GetScaledHeight();
		}
		sp->texinfo.toffs *= TextureTScale(TTex);
		sp->texinfo.toffs += sidedef->TopRowOffset * TextureOffsetTScale(TTex);

		wv[0].x = wv[1].x = seg->v1->x;
		wv[0].y = wv[1].y = seg->v1->y;
		wv[2].x = wv[3].x = seg->v2->x;
		wv[2].y = wv[3].y = seg->v2->y;

		wv[0].z = MAX(back_topz1, botz1);
		wv[1].z = top_topz1;
		wv[2].z = top_topz2;
		wv[3].z = MAX(back_topz2, botz2);

		sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

		sp->frontTopDist = r_ceiling->dist;
		sp->frontBotDist = r_floor->dist;
		sp->backTopDist = back_ceiling->dist;
		sp->backBotDist = back_floor->dist;
		sp->TextureOffset = sidedef->TopTextureOffset;
		sp->RowOffset = sidedef->TopRowOffset;

		//	Sky above top
		dseg->topsky = pspart++;
		if (IsSky(r_ceiling) && !IsSky(back_ceiling))
		{
			sp = dseg->topsky;

			sp->texinfo.Tex = GTextureManager[skyflatnum];
			sp->texinfo.Alpha = 1.1;
			sp->texinfo.Additive = false;
			sp->texinfo.ColourMap = 0;

			wv[0].x = wv[1].x = seg->v1->x;
			wv[0].y = wv[1].y = seg->v1->y;
			wv[2].x = wv[3].x = seg->v2->x;
			wv[2].y = wv[3].y = seg->v2->y;

			wv[0].z = topz1;
			wv[1].z = wv[2].z = skyheight;
			wv[3].z = topz2;

			sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

			sp->frontTopDist = r_ceiling->dist;
		}

		// bottom wall
		dseg->bot = pspart++;
		sp = dseg->bot;

		VTexture* BTex = GTextureManager(sidedef->BottomTexture);
		sp->texinfo.saxis = segdir * TextureSScale(BTex);
		sp->texinfo.taxis = TVec(0, 0, -1) * TextureTScale(BTex);
		sp->texinfo.soffs = -DotProduct(*seg->v1, sp->texinfo.saxis) +
			seg->offset * TextureSScale(BTex) +
			sidedef->BotTextureOffset * TextureOffsetSScale(BTex);
		sp->texinfo.Tex = BTex;
		sp->texinfo.Alpha = 1.1;
		sp->texinfo.Additive = false;
		sp->texinfo.ColourMap = 0;

		if (linedef->flags & ML_DONTPEGBOTTOM)
		{
			// bottom of texture at bottom
			// top of texture at top
			sp->texinfo.toffs = top_TexZ;
		}
		else
		{
			// top of texture at top
			sp->texinfo.toffs = back_floor->TexZ;
		}
		sp->texinfo.toffs *= TextureTScale(BTex);
		sp->texinfo.toffs += sidedef->BotRowOffset * TextureOffsetTScale(BTex);

		wv[0].x = wv[1].x = seg->v1->x;
		wv[0].y = wv[1].y = seg->v1->y;
		wv[2].x = wv[3].x = seg->v2->x;
		wv[2].y = wv[3].y = seg->v2->y;

		wv[0].z = botz1;
		wv[1].z = MIN(back_botz1, topz1);
		wv[2].z = MIN(back_botz2, topz2);
		wv[3].z = botz2;

		sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

		sp->frontTopDist = r_ceiling->dist;
		sp->frontBotDist = r_floor->dist;
		sp->backTopDist = back_ceiling->dist;
		sp->backBotDist = back_floor->dist;
		sp->TextureOffset = sidedef->BotTextureOffset;
		sp->RowOffset = sidedef->BotRowOffset;

		float midtopz1 = topz1;
		float midtopz2 = topz2;
		float midbotz1 = botz1;
		float midbotz2 = botz2;
		if (topz1 > back_topz1 && sidedef->TopTexture > 0)
		{
			midtopz1 = back_topz1;
			midtopz2 = back_topz2;
		}
		if (botz1 < back_botz1 && sidedef->BottomTexture > 0)
		{
			midbotz1 = back_botz1;
			midbotz2 = back_botz2;
		}

		dseg->mid = pspart++;
		sp = dseg->mid;

		VTexture* MTex = GTextureManager(sidedef->MidTexture);
		sp->texinfo.Tex = MTex;
		sp->texinfo.ColourMap = 0;
		if (MTex->Type != TEXTYPE_Null)
		{
			// masked MidTexture
			float texh = MTex->GetScaledHeight();

			float			z_org;

			sp->texinfo.saxis = segdir * TextureSScale(MTex);
			sp->texinfo.taxis = TVec(0, 0, -1) * TextureTScale(MTex);
			sp->texinfo.soffs = -DotProduct(*seg->v1, sp->texinfo.saxis) +
				seg->offset * TextureSScale(MTex) +
				sidedef->MidTextureOffset * TextureOffsetSScale(MTex);
			sp->texinfo.Alpha = linedef->alpha;
			sp->texinfo.Additive = !!(linedef->flags & ML_ADDITIVE);

			if (linedef->flags & ML_DONTPEGBOTTOM)
			{
				// bottom of texture at bottom
				// top of texture at top
				z_org = MAX(seg->frontsector->floor.TexZ,
					seg->backsector->floor.TexZ) + texh;
			}
			else
			{
				// top of texture at top
				z_org = MIN(seg->frontsector->ceiling.TexZ,
					seg->backsector->ceiling.TexZ);
			}
			z_org += sidedef->MidRowOffset * (!MTex->bWorldPanning ?
				1.0 : 1.0 / MTex->TScale);

			sp->texinfo.toffs = z_org * TextureTScale(MTex);

			wv[0].x = wv[1].x = seg->v1->x;
			wv[0].y = wv[1].y = seg->v1->y;
			wv[2].x = wv[3].x = seg->v2->x;
			wv[2].y = wv[3].y = seg->v2->y;

			if (linedef->flags & ML_WRAP_MIDTEX)
			{
				wv[0].z = midbotz1;
				wv[1].z = midtopz1;
				wv[2].z = midtopz2;
				wv[3].z = midbotz2;
			}
			else
			{
				wv[0].z = MAX(midbotz1, z_org - texh);
				wv[1].z = MIN(midtopz1, z_org);
				wv[2].z = MIN(midtopz2, z_org);
				wv[3].z = MAX(midbotz2, z_org - texh);
			}

			sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);
		}

		sp->frontTopDist = r_ceiling->dist;
		sp->frontBotDist = r_floor->dist;
		sp->backTopDist = back_ceiling->dist;
		sp->backBotDist = back_floor->dist;
		sp->TextureOffset = sidedef->MidTextureOffset;
		sp->RowOffset = sidedef->MidRowOffset;

		sec_region_t *reg;
		for (reg = seg->backsector->topregion; reg->prev; reg = reg->prev)
		{
			sp = pspart++;
			sp->next = dseg->extra;
			dseg->extra = sp;

			sec_plane_t* extratop = reg->floor;
			sec_plane_t* extrabot = reg->prev->ceiling;
			side_t *extraside = &Level->Sides[reg->prev->extraline->sidenum[0]];

			float extratopz1 = extratop->GetPointZ(*seg->v1);
			float extratopz2 = extratop->GetPointZ(*seg->v2);
			float extrabotz1 = extrabot->GetPointZ(*seg->v1);
			float extrabotz2 = extrabot->GetPointZ(*seg->v2);

			VTexture* MTex = GTextureManager(extraside->MidTexture);
			sp->texinfo.saxis = segdir * TextureSScale(MTex);
			sp->texinfo.taxis = TVec(0, 0, -1) * TextureTScale(MTex);
			sp->texinfo.soffs = -DotProduct(*seg->v1, sp->texinfo.saxis) +
				seg->offset * TextureSScale(MTex) +
				sidedef->MidTextureOffset * TextureOffsetSScale(MTex);

			sp->texinfo.toffs = extratop->TexZ *
				TextureTScale(MTex) + sidedef->MidRowOffset *
				TextureOffsetTScale(MTex);
			sp->texinfo.Tex = MTex;
			sp->texinfo.Alpha = extrabot->Alpha < 1.0 ? extrabot->Alpha : 1.1;
			sp->texinfo.Additive = !!(extrabot->flags & SPF_ADDITIVE);
			sp->texinfo.ColourMap = 0;

			wv[0].x = wv[1].x = seg->v1->x;
			wv[0].y = wv[1].y = seg->v1->y;
			wv[2].x = wv[3].x = seg->v2->x;
			wv[2].y = wv[3].y = seg->v2->y;

			wv[0].z = MAX(extrabotz1, botz1);
			wv[1].z = MIN(extratopz1, topz1);
			wv[2].z = MIN(extratopz2, topz2);
			wv[3].z = MAX(extrabotz2, botz2);

			sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

			sp->frontTopDist = r_ceiling->dist;
			sp->frontBotDist = r_floor->dist;
			sp->backTopDist = extratop->dist;
			sp->backBotDist = extrabot->dist;
			sp->TextureOffset = sidedef->MidTextureOffset;
			sp->RowOffset = sidedef->MidRowOffset;
		}
	}
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::UpdateRowOffset
//
//==========================================================================

void VRenderLevelShared::UpdateRowOffset(segpart_t *sp, float RowOffset)
{
	guard(VRenderLevelShared::UpdateRowOffset);
	sp->texinfo.toffs += (RowOffset - sp->RowOffset) *
		TextureOffsetTScale(sp->texinfo.Tex);
	sp->RowOffset = RowOffset;
	FlushSurfCaches(sp->surfs);
	InitSurfs(sp->surfs, &sp->texinfo, NULL, r_sub);
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::UpdateTextureOffset
//
//==========================================================================

void VRenderLevelShared::UpdateTextureOffset(segpart_t* sp,
	float TextureOffset)
{
	guard(VRenderLevelShared::UpdateTextureOffset);
	sp->texinfo.soffs += (TextureOffset - sp->TextureOffset) *
		TextureOffsetSScale(sp->texinfo.Tex);
	sp->TextureOffset = TextureOffset;
	FlushSurfCaches(sp->surfs);
	InitSurfs(sp->surfs, &sp->texinfo, NULL, r_sub);
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::UpdateDrawSeg
//
//==========================================================================

void VRenderLevelShared::UpdateDrawSeg(drawseg_t* dseg, bool ShouldClip)
{
	guard(VRenderLevelShared::UpdateDrawSeg);
	seg_t *seg = dseg->seg;
	segpart_t *sp;
	TVec wv[4];

	if (!seg->linedef)
	{
		//	Miniseg
		return;
	}

	side_t* sidedef = seg->sidedef;
	line_t* linedef = seg->linedef;

	if (ShouldClip)
	{
		float a1 = ViewClip.PointToClipAngle(*seg->v2);
		float a2 = ViewClip.PointToClipAngle(*seg->v1);
		if (!ViewClip.IsRangeVisible(a1, a2))
		{
			return;
		}
	}

	if (!seg->backsector)
	{
		sp = dseg->mid;
		sp->texinfo.ColourMap = ColourMap;
		VTexture* MTex = GTextureManager(sidedef->MidTexture);
		if (FASI(sp->frontTopDist) != FASI(r_ceiling->dist) ||
			FASI(sp->frontBotDist) != FASI(r_floor->dist) ||
			sp->texinfo.Tex->SScale != MTex->SScale ||
			sp->texinfo.Tex->TScale != MTex->TScale ||
			sp->texinfo.Tex->GetHeight() != MTex->GetHeight())
		{
			float topz1 = r_ceiling->GetPointZ(*seg->v1);
			float topz2 = r_ceiling->GetPointZ(*seg->v2);
			float botz1 = r_floor->GetPointZ(*seg->v1);
			float botz2 = r_floor->GetPointZ(*seg->v2);

			FreeWSurfs(sp->surfs);
			sp->surfs = NULL;

			if (linedef->flags & ML_DONTPEGBOTTOM)
			{
				//	bottom of texture at bottom
				sp->texinfo.toffs = r_floor->TexZ +
					MTex->GetScaledHeight();
			}
			else if (linedef->flags & ML_DONTPEGTOP)
			{
				// top of texture at top of top region
				sp->texinfo.toffs =
					r_sub->sector->topregion->ceiling->TexZ;
			}
			else
			{
				// top of texture at top
				sp->texinfo.toffs = r_ceiling->TexZ;
			}
			sp->texinfo.toffs *= TextureTScale(MTex);
			sp->texinfo.toffs += sidedef->MidRowOffset * TextureOffsetTScale(MTex);
			sp->texinfo.Tex = MTex;

			wv[0].x = wv[1].x = seg->v1->x;
			wv[0].y = wv[1].y = seg->v1->y;
			wv[2].x = wv[3].x = seg->v2->x;
			wv[2].y = wv[3].y = seg->v2->y;

			wv[0].z = botz1;
			wv[1].z = topz1;
			wv[2].z = topz2;
			wv[3].z = botz2;

			sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

			sp->frontTopDist = r_ceiling->dist;
			sp->frontBotDist = r_floor->dist;
			sp->RowOffset = sidedef->MidRowOffset;
		}
		else if (FASI(sp->RowOffset) != FASI(sidedef->MidRowOffset))
		{
			sp->texinfo.Tex = MTex;
			UpdateRowOffset(sp, sidedef->MidRowOffset);
		}
		else
		{
			sp->texinfo.Tex = MTex;
		}
		if (FASI(sp->TextureOffset) != FASI(sidedef->MidTextureOffset))
		{
			UpdateTextureOffset(sp, sidedef->MidTextureOffset);
		}

		sp = dseg->topsky;
		sp->texinfo.ColourMap = ColourMap;
		if (IsSky(r_ceiling) &&
			FASI(sp->frontTopDist) != FASI(r_ceiling->dist))
		{
			float topz1 = r_ceiling->GetPointZ(*seg->v1);
			float topz2 = r_ceiling->GetPointZ(*seg->v2);

			FreeWSurfs(sp->surfs);
			sp->surfs = NULL;

			wv[0].x = wv[1].x = seg->v1->x;
			wv[0].y = wv[1].y = seg->v1->y;
			wv[2].x = wv[3].x = seg->v2->x;
			wv[2].y = wv[3].y = seg->v2->y;

			wv[0].z = topz1;
			wv[1].z = wv[2].z = skyheight;
			wv[3].z = topz2;

			sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

			sp->frontTopDist = r_ceiling->dist;
		}
	}
	else
	{
		sec_plane_t* back_floor = &seg->backsector->floor;
		sec_plane_t* back_ceiling = &seg->backsector->ceiling;
		if (seg->backsector->fakefloors)
		{
			if (back_floor == &seg->backsector->floor)
			{
				back_floor = &seg->backsector->fakefloors->floorplane;
			}
			if (back_ceiling == &seg->backsector->ceiling)
			{
				back_ceiling = &seg->backsector->fakefloors->ceilplane;
			}
		}

		// top wall
		sp = dseg->top;
		sp->texinfo.ColourMap = ColourMap;
		VTexture* TTex = GTextureManager(sidedef->TopTexture);
		if (IsSky(r_ceiling) && IsSky(back_ceiling) &&
			r_ceiling->SkyBox != back_ceiling->SkyBox)
		{
			TTex = GTextureManager[skyflatnum];
		}
		if (FASI(sp->frontTopDist) != FASI(r_ceiling->dist) ||
			FASI(sp->frontBotDist) != FASI(r_floor->dist) ||
			FASI(sp->backTopDist) != FASI(back_ceiling->dist) ||
			sp->texinfo.Tex->SScale != TTex->SScale ||
			sp->texinfo.Tex->TScale != TTex->TScale)
		{
			float topz1 = r_ceiling->GetPointZ(*seg->v1);
			float topz2 = r_ceiling->GetPointZ(*seg->v2);
			float botz1 = r_floor->GetPointZ(*seg->v1);
			float botz2 = r_floor->GetPointZ(*seg->v2);

			float back_topz1 = back_ceiling->GetPointZ(*seg->v1);
			float back_topz2 = back_ceiling->GetPointZ(*seg->v2);

			// hack to allow height changes in outdoor areas
			float top_topz1 = topz1;
			float top_topz2 = topz2;
			float top_TexZ = r_ceiling->TexZ;
			if (IsSky(r_ceiling) && IsSky(back_ceiling) &&
				r_ceiling->SkyBox == back_ceiling->SkyBox)
			{
				top_topz1 = back_topz1;
				top_topz2 = back_topz2;
				top_TexZ = back_ceiling->TexZ;
			}

			FreeWSurfs(sp->surfs);
			sp->surfs = NULL;

			if (linedef->flags & ML_DONTPEGTOP)
			{
				// top of texture at top
				sp->texinfo.toffs = top_TexZ;
			}
			else
			{
				// bottom of texture
				sp->texinfo.toffs = back_ceiling->TexZ + TTex->GetScaledHeight();
			}
			sp->texinfo.toffs *= TextureTScale(TTex);
			sp->texinfo.toffs += sidedef->TopRowOffset * TextureOffsetTScale(TTex);
			sp->texinfo.Tex = TTex;

			wv[0].x = wv[1].x = seg->v1->x;
			wv[0].y = wv[1].y = seg->v1->y;
			wv[2].x = wv[3].x = seg->v2->x;
			wv[2].y = wv[3].y = seg->v2->y;

			wv[0].z = MAX(back_topz1, botz1);
			wv[1].z = top_topz1;
			wv[2].z = top_topz2;
			wv[3].z = MAX(back_topz2, botz2);

			sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

			sp->frontTopDist = r_ceiling->dist;
			sp->frontBotDist = r_floor->dist;
			sp->backTopDist = back_ceiling->dist;
			sp->RowOffset = sidedef->TopRowOffset;
		}
		else if (FASI(sp->RowOffset) != FASI(sidedef->TopRowOffset))
		{
			sp->texinfo.Tex = TTex;
			UpdateRowOffset(sp, sidedef->TopRowOffset);
		}
		else
		{
			sp->texinfo.Tex = TTex;
		}
		if (FASI(sp->TextureOffset) != FASI(sidedef->TopTextureOffset))
		{
			UpdateTextureOffset(sp, sidedef->TopTextureOffset);
		}

		//	Sky above top
		sp = dseg->topsky;
		sp->texinfo.ColourMap = ColourMap;
		if (IsSky(r_ceiling) && !IsSky(back_ceiling) &&
			FASI(sp->frontTopDist) != FASI(r_ceiling->dist))
		{
			float topz1 = r_ceiling->GetPointZ(*seg->v1);
			float topz2 = r_ceiling->GetPointZ(*seg->v2);

			FreeWSurfs(sp->surfs);
			sp->surfs = NULL;

			sp->texinfo.Tex = GTextureManager[skyflatnum];
			sp->texinfo.Alpha = 1.1;
			sp->texinfo.Additive = false;
			sp->texinfo.ColourMap = 0;

			wv[0].x = wv[1].x = seg->v1->x;
			wv[0].y = wv[1].y = seg->v1->y;
			wv[2].x = wv[3].x = seg->v2->x;
			wv[2].y = wv[3].y = seg->v2->y;

			wv[0].z = topz1;
			wv[1].z = wv[2].z = skyheight;
			wv[3].z = topz2;

			sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

			sp->frontTopDist = r_ceiling->dist;
		}

		// bottom wall
		sp = dseg->bot;
		sp->texinfo.ColourMap = ColourMap;
		VTexture* BTex = GTextureManager(sidedef->BottomTexture);
		sp->texinfo.Tex = BTex;
		if (FASI(sp->frontTopDist) != FASI(r_ceiling->dist) ||
			FASI(sp->frontBotDist) != FASI(r_floor->dist) ||
			FASI(sp->backBotDist) != FASI(back_floor->dist))
		{
			float topz1 = r_ceiling->GetPointZ(*seg->v1);
			float topz2 = r_ceiling->GetPointZ(*seg->v2);
			float botz1 = r_floor->GetPointZ(*seg->v1);
			float botz2 = r_floor->GetPointZ(*seg->v2);
			float top_TexZ = r_ceiling->TexZ;

			float back_botz1 = back_floor->GetPointZ(*seg->v1);
			float back_botz2 = back_floor->GetPointZ(*seg->v2);

			// hack to allow height changes in outdoor areas
			if (IsSky(r_ceiling) && IsSky(back_ceiling))
			{
				topz1 = back_ceiling->GetPointZ(*seg->v1);
				topz2 = back_ceiling->GetPointZ(*seg->v2);
				top_TexZ = back_ceiling->TexZ;
			}

			FreeWSurfs(sp->surfs);
			sp->surfs = NULL;

			if (linedef->flags & ML_DONTPEGBOTTOM)
			{
				// bottom of texture at bottom
				// top of texture at top
				sp->texinfo.toffs = top_TexZ;
			}
			else
			{
				// top of texture at top
				sp->texinfo.toffs = back_floor->TexZ;
			}
			sp->texinfo.toffs *= TextureTScale(BTex);
			sp->texinfo.toffs += sidedef->BotRowOffset *
				TextureOffsetTScale(BTex);

			wv[0].x = wv[1].x = seg->v1->x;
			wv[0].y = wv[1].y = seg->v1->y;
			wv[2].x = wv[3].x = seg->v2->x;
			wv[2].y = wv[3].y = seg->v2->y;

			wv[0].z = botz1;
			wv[1].z = MIN(back_botz1, topz1);
			wv[2].z = MIN(back_botz2, topz2);
			wv[3].z = botz2;

			sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

			sp->frontTopDist = r_ceiling->dist;
			sp->frontBotDist = r_floor->dist;
			sp->backBotDist = back_floor->dist;
			sp->RowOffset = sidedef->BotRowOffset;
		}
		else if (FASI(sp->RowOffset) != FASI(sidedef->BotRowOffset))
		{
			UpdateRowOffset(sp, sidedef->BotRowOffset);
		}
		if (FASI(sp->TextureOffset) != FASI(sidedef->BotTextureOffset))
		{
			UpdateTextureOffset(sp, sidedef->BotTextureOffset);
		}

		// masked MidTexture
		sp = dseg->mid;
		sp->texinfo.ColourMap = ColourMap;
		VTexture* MTex = GTextureManager(sidedef->MidTexture);
		if (FASI(sp->frontTopDist) != FASI(r_ceiling->dist) ||
			FASI(sp->frontBotDist) != FASI(r_floor->dist) ||
			FASI(sp->backTopDist) != FASI(back_ceiling->dist) ||
			FASI(sp->backBotDist) != FASI(back_floor->dist) ||
			FASI(sp->RowOffset) != FASI(sidedef->MidRowOffset) ||
			sp->texinfo.Tex->SScale != MTex->SScale ||
			sp->texinfo.Tex->TScale != MTex->TScale ||
			sp->texinfo.Tex->GetHeight() != MTex->GetHeight() ||
			(sp->texinfo.Tex->Type == TEXTYPE_Null) != (MTex->Type == TEXTYPE_Null))
		{
			FreeWSurfs(sp->surfs);
			sp->surfs = NULL;

			sp->texinfo.Tex = MTex;
			if (sidedef->MidTexture)
			{
				float topz1 = r_ceiling->GetPointZ(*seg->v1);
				float topz2 = r_ceiling->GetPointZ(*seg->v2);
				float botz1 = r_floor->GetPointZ(*seg->v1);
				float botz2 = r_floor->GetPointZ(*seg->v2);

				float back_topz1 = back_ceiling->GetPointZ(*seg->v1);
				float back_topz2 = back_ceiling->GetPointZ(*seg->v2);
				float back_botz1 = back_floor->GetPointZ(*seg->v1);
				float back_botz2 = back_floor->GetPointZ(*seg->v2);

				float midtopz1 = topz1;
				float midtopz2 = topz2;
				float midbotz1 = botz1;
				float midbotz2 = botz2;
				if (topz1 > back_topz1 && sidedef->TopTexture > 0)
				{
					midtopz1 = back_topz1;
					midtopz2 = back_topz2;
				}
				if (botz1 < back_botz1 && sidedef->BottomTexture > 0)
				{
					midbotz1 = back_botz1;
					midbotz2 = back_botz2;
				}

				float texh = MTex->GetScaledHeight();

				float			z_org;

				TVec segdir = (*seg->v2 - *seg->v1) / seg->length;

				sp->texinfo.saxis = segdir * TextureSScale(MTex);
				sp->texinfo.taxis = TVec(0, 0, -1) * TextureTScale(MTex);
				sp->texinfo.soffs = -DotProduct(*seg->v1, sp->texinfo.saxis) +
					seg->offset * TextureSScale(MTex) +
					sidedef->MidTextureOffset * TextureOffsetSScale(MTex);
				sp->texinfo.Alpha = linedef->alpha;
				sp->texinfo.Additive = !!(linedef->flags & ML_ADDITIVE);

				if (linedef->flags & ML_DONTPEGBOTTOM)
				{
					// bottom of texture at bottom
					// top of texture at top
					z_org = MAX(seg->frontsector->floor.TexZ,
						seg->backsector->floor.TexZ) + texh;
				}
				else
				{
					// top of texture at top
					z_org = MIN(seg->frontsector->ceiling.TexZ,
						seg->backsector->ceiling.TexZ);
				}
				z_org += sidedef->MidRowOffset * (!MTex->bWorldPanning ?
					1.0 : 1.0 / MTex->TScale);

				sp->texinfo.toffs = z_org * TextureTScale(MTex);

				wv[0].x = wv[1].x = seg->v1->x;
				wv[0].y = wv[1].y = seg->v1->y;
				wv[2].x = wv[3].x = seg->v2->x;
				wv[2].y = wv[3].y = seg->v2->y;

				if (linedef->flags & ML_WRAP_MIDTEX)
				{
					wv[0].z = midbotz1;
					wv[1].z = midtopz1;
					wv[2].z = midtopz2;
					wv[3].z = midbotz2;
				}
				else
				{
					wv[0].z = MAX(midbotz1, z_org - texh);
					wv[1].z = MIN(midtopz1, z_org);
					wv[2].z = MIN(midtopz2, z_org);
					wv[3].z = MAX(midbotz2, z_org - texh);
				}

				sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);
			}
			else
			{
				sp->texinfo.Alpha = 1.1;
				sp->texinfo.Additive = false;
			}

			sp->frontTopDist = r_ceiling->dist;
			sp->frontBotDist = r_floor->dist;
			sp->backTopDist = back_ceiling->dist;
			sp->backBotDist = back_floor->dist;
			sp->RowOffset = sidedef->MidRowOffset;
		}
		else
		{
			sp->texinfo.Tex = MTex;
			if (sidedef->MidTexture)
			{
				sp->texinfo.Alpha = linedef->alpha;
				sp->texinfo.Additive = !!(linedef->flags & ML_ADDITIVE);
			}
		}
		if (FASI(sp->TextureOffset) != FASI(sidedef->MidTextureOffset))
		{
			UpdateTextureOffset(sp, sidedef->MidTextureOffset);
		}

		sec_region_t *reg;
		segpart_t *sp = dseg->extra;
		for (reg = seg->backsector->botregion; reg->next; reg = reg->next)
		{
			sec_plane_t* extratop = reg->next->floor;
			sec_plane_t* extrabot = reg->ceiling;
			side_t *extraside = &Level->Sides[reg->extraline->sidenum[0]];

			sp->texinfo.ColourMap = ColourMap;
			VTexture* ETex = GTextureManager(extraside->MidTexture);
			sp->texinfo.Tex = ETex;
			if (FASI(sp->frontTopDist) != FASI(r_ceiling->dist) ||
				FASI(sp->frontBotDist) != FASI(r_floor->dist) ||
				FASI(sp->backTopDist) != FASI(extratop->dist) ||
				FASI(sp->backBotDist) != FASI(extrabot->dist))
			{
				float topz1 = r_ceiling->GetPointZ(*seg->v1);
				float topz2 = r_ceiling->GetPointZ(*seg->v2);
				float botz1 = r_floor->GetPointZ(*seg->v1);
				float botz2 = r_floor->GetPointZ(*seg->v2);

				float extratopz1 = extratop->GetPointZ(*seg->v1);
				float extratopz2 = extratop->GetPointZ(*seg->v2);
				float extrabotz1 = extrabot->GetPointZ(*seg->v1);
				float extrabotz2 = extrabot->GetPointZ(*seg->v2);

				FreeWSurfs(sp->surfs);
				sp->surfs = NULL;

				sp->texinfo.toffs = extratop->TexZ *
					TextureTScale(ETex) + sidedef->MidRowOffset *
					TextureOffsetTScale(ETex);

				wv[0].x = wv[1].x = seg->v1->x;
				wv[0].y = wv[1].y = seg->v1->y;
				wv[2].x = wv[3].x = seg->v2->x;
				wv[2].y = wv[3].y = seg->v2->y;

				wv[0].z = MAX(extrabotz1, botz1);
				wv[1].z = MIN(extratopz1, topz1);
				wv[2].z = MIN(extratopz2, topz2);
				wv[3].z = MAX(extrabotz2, botz2);

				sp->surfs = CreateWSurfs(wv, &sp->texinfo, seg, r_sub);

				sp->frontTopDist = r_ceiling->dist;
				sp->frontBotDist = r_floor->dist;
				sp->backTopDist = extratop->dist;
				sp->backBotDist = extrabot->dist;
				sp->RowOffset = sidedef->MidRowOffset;
			}
			else if (FASI(sp->RowOffset) != FASI(sidedef->MidRowOffset))
			{
				UpdateRowOffset(sp, sidedef->MidRowOffset);
			}
			if (FASI(sp->TextureOffset) != FASI(sidedef->MidTextureOffset))
			{
				UpdateTextureOffset(sp, sidedef->MidTextureOffset);
			}
			sp = sp->next;
		}
	}
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::SegMoved
//
//==========================================================================

void VRenderLevelShared::SegMoved(seg_t* seg)
{
	guard(VRenderLevelShared::SegMoved);
	if (!seg->drawsegs)
	{
		//	Drawsegs not created yet
		return;
	}
	if (!seg->linedef)
	{
		Sys_Error("R_SegMoved: miniseg");
	}

	VTexture* Tex = seg->drawsegs->mid->texinfo.Tex;
	seg->drawsegs->mid->texinfo.saxis = (*seg->v2 - *seg->v1) / seg->length *
		TextureSScale(Tex);
	seg->drawsegs->mid->texinfo.soffs = -DotProduct(*seg->v1,
		seg->drawsegs->mid->texinfo.saxis) +
		seg->offset * TextureSScale(Tex) +
		seg->sidedef->MidTextureOffset * TextureOffsetSScale(Tex);

	//	Force update
	seg->drawsegs->mid->frontTopDist += 0.346;
	unguard;
}

//==========================================================================
//
//	VRenderLevel::PreRender
//
//==========================================================================

void VRenderLevel::PreRender()
{
	guard(VRenderLevel::PreRender);
	c_subdivides = 0;
	c_seg_div = 0;
	light_mem = 0;

	CreateWorldSurfaces();

	GCon->Logf(NAME_Dev, "%d subdivides", c_subdivides);
	GCon->Logf(NAME_Dev, "%d seg subdivides", c_seg_div);
	GCon->Logf(NAME_Dev, "%dk light mem", light_mem / 1024);
	unguard;
}

//==========================================================================
//
//	VAdvancedRenderLevel::PreRender
//
//==========================================================================

void VAdvancedRenderLevel::PreRender()
{
	guard(VAdvancedRenderLevel::PreRender);
	CreateWorldSurfaces();
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::CreateWorldSurfaces
//
//==========================================================================

void VRenderLevelShared::CreateWorldSurfaces()
{
	guard(VRenderLevelShared::CreateWorldSurfaces);
	int				i, j;
	int				count, dscount, spcount;
	subregion_t		*sreg;
	subsector_t		*sub;
	drawseg_t		*pds;
	sec_region_t	*reg;

	free_wsurfs = NULL;
	SetupSky();

	//	Set up fake floors.
	for (i = 0; i < Level->NumSectors; i++)
	{
		if (Level->Sectors[i].heightsec)
		{
			SetupFakeFloors(&Level->Sectors[i]);
		}
	}

	//	Count regions in all subsectors
	count = 0;
	dscount = 0;
	spcount = 0;
	for (i = 0; i < Level->NumSubsectors; i++)
	{
		sub = &Level->Subsectors[i];
		if (!sub->sector->linecount)
		{
			//	Skip sectors containing original polyobjs
			continue;
		}
		for (reg = sub->sector->botregion; reg; reg = reg->next)		
		{
			count++;
			dscount += sub->numlines;
			if (sub->poly)
			{
				//	Polyobj
				dscount += sub->poly->numsegs;
			}
			for (j = 0; j < sub->numlines; j++)
			{
				spcount += CountSegParts(&Level->Segs[sub->firstline + j]);
			}
			if (sub->poly)
			{
				int polyCount = sub->poly->numsegs;
				seg_t **polySeg = sub->poly->segs;
				while (polyCount--)
				{
					spcount += CountSegParts(*polySeg);
					polySeg++;
				}
			}
		}
	}

	//	Get some memory
	sreg = new subregion_t[count];
	pds = new drawseg_t[dscount];
	pspart = new segpart_t[spcount];
	memset(sreg, 0, sizeof(subregion_t) * count);
	memset(pds, 0, sizeof(drawseg_t) * dscount);
	memset(pspart, 0, sizeof(segpart_t) * spcount);
	AllocatedSubRegions = sreg;
	AllocatedDrawSegs = pds;
	AllocatedSegParts = pspart;

	//	Add dplanes
	for (i = 0; i < Level->NumSubsectors; i++)
	{
		if (!(i & 7))
		{
			CL_KeepaliveMessage();
		}
		sub = &Level->Subsectors[i];
		if (!sub->sector->linecount)
		{
			//	Skip sectors containing original polyobjs
			continue;
		}
		r_sub = sub;
		for (reg = sub->sector->botregion; reg; reg = reg->next)
		{
			r_floor = reg->floor;
			r_ceiling = reg->ceiling;
			if (sub->sector->fakefloors)
			{
				if (r_floor == &sub->sector->floor)
				{
					r_floor = &sub->sector->fakefloors->floorplane;
				}
				if (r_ceiling == &sub->sector->ceiling)
				{
					r_ceiling = &sub->sector->fakefloors->ceilplane;
				}
			}

			sreg->secregion = reg;
			sreg->floorplane = r_floor;
			sreg->ceilplane = r_ceiling;
			sreg->floor = CreateSecSurface(sub, r_floor);
			sreg->ceil = CreateSecSurface(sub, r_ceiling);

			sreg->count = sub->numlines;
			if (sub->poly)
			{
				//	Polyobj
				sreg->count += sub->poly->numsegs;
			}
			sreg->lines = pds;
			pds += sreg->count;
			for (j = 0; j < sub->numlines; j++)
			{
				CreateSegParts(&sreg->lines[j], &Level->Segs[sub->firstline + j]);
			}
			if (sub->poly)
			{
				//	Polyobj
				int polyCount = sub->poly->numsegs;
				seg_t **polySeg = sub->poly->segs;
				while (polyCount--)
				{
					CreateSegParts(&sreg->lines[j], *polySeg);
					polySeg++;
					j++;
				}
			}

			sreg->next = sub->regions;
			sub->regions = sreg;
			sreg++;
		}
	}
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::UpdateSubRegion
//
//==========================================================================

void VRenderLevelShared::UpdateSubRegion(subregion_t* region, bool ClipSegs)
{
	guard(VRenderLevelShared::UpdateSubRegion);
	int				count;
	int 			polyCount;
	seg_t**			polySeg;

	r_floor = region->floorplane;
	r_ceiling = region->ceilplane;
	if (r_sub->sector->fakefloors)
	{
		if (r_floor == &r_sub->sector->floor)
		{
			r_floor = &r_sub->sector->fakefloors->floorplane;
		}
		if (r_ceiling == &r_sub->sector->ceiling)
		{
			r_ceiling = &r_sub->sector->fakefloors->ceilplane;
		}
	}

	count = r_sub->numlines;
	drawseg_t *ds = region->lines;
	while (count--)
	{
		UpdateDrawSeg(ds, ClipSegs);
		ds++;
	}

	UpdateSecSurface(region->floor, region->floorplane, r_sub);
	UpdateSecSurface(region->ceil, region->ceilplane, r_sub);

	if (r_sub->poly)
	{
		//	Update the polyobj
		polyCount = r_sub->poly->numsegs;
		polySeg = r_sub->poly->segs;
		while (polyCount--)
		{
			UpdateDrawSeg((*polySeg)->drawsegs, ClipSegs);
			polySeg++;
		}
	}

	if (region->next)
	{
		UpdateSubRegion(region->next, ClipSegs);
	}
	unguard;
}

//==========================================================================
//
//	VRenderLevel::UpdateSubsector
//
//==========================================================================

void VRenderLevel::UpdateSubsector(int num, float *bbox)
{
	guard(VRenderLevel::UpdateSubsector);
	r_sub = &Level->Subsectors[num];

	if (r_sub->VisFrame != r_visframecount)
	{
		return;
	}

	if (!r_sub->sector->linecount)
	{
		//	Skip sectors containing original polyobjs
		return;
	}

	if (!ViewClip.ClipCheckSubsector(r_sub))
	{
		return;
	}

	bbox[2] = r_sub->sector->floor.minz;
	if (IsSky(&r_sub->sector->ceiling))
	{
		bbox[5] = skyheight;
	}
	else
	{
		bbox[5] = r_sub->sector->ceiling.maxz;
	}

	UpdateSubRegion(r_sub->regions, true);

	ViewClip.ClipAddSubsectorSegs(r_sub);
	unguard;
}

//==========================================================================
//
//	VAdvancedRenderLevel::UpdateSubsector
//
//==========================================================================

void VAdvancedRenderLevel::UpdateSubsector(int num, float *bbox)
{
	guard(VAdvancedRenderLevel::UpdateSubsector);
	r_sub = &Level->Subsectors[num];

	if (!r_sub->sector->linecount)
	{
		//	Skip sectors containing original polyobjs
		return;
	}

	bbox[2] = r_sub->sector->floor.minz;
	if (IsSky(&r_sub->sector->ceiling))
	{
		bbox[5] = skyheight;
	}
	else
	{
		bbox[5] = r_sub->sector->ceiling.maxz;
	}

	UpdateSubRegion(r_sub->regions, false);
	unguard;
}

//==========================================================================
//
//	VRenderLevel::UpdateBSPNode
//
//==========================================================================

void VRenderLevel::UpdateBSPNode(int bspnum, float* bbox)
{
	guard(VRenderLevel::UpdateBSPNode);
	if (ViewClip.ClipIsFull())
	{
		return;
	}

	if (!ViewClip.ClipIsBBoxVisible(bbox))
	{
		return;
	}

	// Found a subsector?
	if (bspnum & NF_SUBSECTOR)
	{
		if (bspnum == -1)
		{
			UpdateSubsector(0, bbox);
		}
		else
		{
			UpdateSubsector(bspnum & (~NF_SUBSECTOR), bbox);
		}
		return;
	}

	node_t* bsp = &Level->Nodes[bspnum];

	if (bsp->VisFrame != r_visframecount)
	{
		return;
	}

	// Decide which side the view point is on.
	int side = bsp->PointOnSide(vieworg);

	UpdateBSPNode(bsp->children[side], bsp->bbox[side]);
	UpdateBSPNode(bsp->children[side ^ 1], bsp->bbox[side ^ 1]);
	bbox[2] = MIN(bsp->bbox[0][2], bsp->bbox[1][2]);
	bbox[5] = MAX(bsp->bbox[0][5], bsp->bbox[1][5]);
	unguard;
}

//==========================================================================
//
//	VAdvancedRenderLevel::UpdateBSPNode
//
//==========================================================================

void VAdvancedRenderLevel::UpdateBSPNode(int bspnum, float* bbox)
{
	guard(VAdvancedRenderLevel::UpdateBSPNode);
	// Found a subsector?
	if (bspnum & NF_SUBSECTOR)
	{
		if (bspnum == -1)
		{
			UpdateSubsector(0, bbox);
		}
		else
		{
			UpdateSubsector(bspnum & (~NF_SUBSECTOR), bbox);
		}
		return;
	}

	node_t* bsp = &Level->Nodes[bspnum];

	// Decide which side the view point is on.
	int side = bsp->PointOnSide(vieworg);

	UpdateBSPNode(bsp->children[side], bsp->bbox[side]);
	UpdateBSPNode(bsp->children[side ^ 1], bsp->bbox[side ^ 1]);
	bbox[2] = MIN(bsp->bbox[0][2], bsp->bbox[1][2]);
	bbox[5] = MAX(bsp->bbox[0][5], bsp->bbox[1][5]);
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::CopyPlaneIfValid
//
//==========================================================================

bool VRenderLevelShared::CopyPlaneIfValid(sec_plane_t* dest,
	const sec_plane_t* source, const sec_plane_t* opp)
{
	guard(VRenderLevelShared::CopyPlaneIfValid);
	bool copy = false;

	// If the planes do not have matching slopes, then always copy them
	// because clipping would require creating new sectors.
	if (source->normal != dest->normal)
	{
		copy = true;
	}
	else if (opp->normal != -dest->normal)
	{
		if (source->dist > dest->dist)
		{
			copy = true;
		}
	}
	else if (source->dist > dest->dist && source->dist < -opp->dist)
	{
		copy = true;
	}

	if (copy)
	{
		*(TPlane*)dest = *(TPlane*)source;
	}

	return copy;
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::UpdateFakeFlats
//
// killough 3/7/98: Hack floor/ceiling heights for deep water etc.
//
// If player's view height is underneath fake floor, lower the
// drawn ceiling to be just under the floor height, and replace
// the drawn floor and ceiling textures, and light level, with
// the control sector's.
//
// Similar for ceiling, only reflected.
//
// killough 4/11/98, 4/13/98: fix bugs, add 'back' parameter
//
//==========================================================================

void VRenderLevelShared::UpdateFakeFlats(sector_t* sec)
{
	guard(VRenderLevelShared::UpdateFakeFlats);
	const sector_t *s = sec->heightsec;
	sector_t *heightsec = r_viewleaf->sector->heightsec;
	bool underwater = /*r_fakingunderwater ||*/
		//(heightsec && vieworg.z <= heightsec->floor.GetPointZ(vieworg));
		(s && vieworg.z <= s->floor.GetPointZ(vieworg));
	bool doorunderwater = false;
	int diffTex = !!(s->SectorFlags & sector_t::SF_ClipFakePlanes);

	// Replace sector being drawn with a copy to be hacked
	fakefloor_t* ff = sec->fakefloors;
	ff->floorplane = sec->floor;
	ff->ceilplane = sec->ceiling;
	ff->params = sec->params;

	// Replace floor and ceiling height with control sector's heights.
	if (diffTex)
	{
		if (CopyPlaneIfValid(&ff->floorplane, &s->floor, &sec->ceiling))
		{
			ff->floorplane.pic = s->floor.pic;
		}
		else if (s->SectorFlags & sector_t::SF_FakeFloorOnly)
		{
			if (underwater)
			{
//				tempsec->ColourMap = s->ColourMap;
				ff->params.Fade = s->params.Fade;
				if (!(s->SectorFlags & sector_t::SF_NoFakeLight))
				{
					ff->params.lightlevel = s->params.lightlevel;
					ff->params.LightColour = s->params.LightColour;

/*					if (floorlightlevel != NULL)
					{
						*floorlightlevel = GetFloorLight (s);
					}

					if (ceilinglightlevel != NULL)
					{
						*ceilinglightlevel = GetFloorLight (s);
					}*/
				}
				return;
			}
			return;
		}
	}
	else
	{
		ff->floorplane.normal = s->floor.normal;
		ff->floorplane.dist = s->floor.dist;
	}

	if (!(s->SectorFlags & sector_t::SF_FakeFloorOnly))
	{
		if (diffTex)
		{
			if (CopyPlaneIfValid(&ff->ceilplane, &s->ceiling, &sec->floor))
			{
				ff->ceilplane.pic = s->ceiling.pic;
			}
		}
		else
		{
			ff->ceilplane.normal = s->ceiling.normal;
			ff->ceilplane.dist = s->ceiling.dist;
		}
	}

//	float refflorz = s->floor.GetPointZ(viewx, viewy);
	float refceilz = s->ceiling.GetPointZ(vieworg);
//	float orgflorz = sec->floor.GetPointZ(viewx, viewy);
	float orgceilz = sec->ceiling.GetPointZ(vieworg);

#if 0//1
	// [RH] Allow viewing underwater areas through doors/windows that
	// are underwater but not in a water sector themselves.
	// Only works if you cannot see the top surface of any deep water
	// sectors at the same time.
	if (back && !r_fakingunderwater && curline->frontsector->heightsec == NULL)
	{
		if (rw_frontcz1 <= s->floorplane.ZatPoint (curline->v1->x, curline->v1->y) &&
			rw_frontcz2 <= s->floorplane.ZatPoint (curline->v2->x, curline->v2->y))
		{
			// Check that the window is actually visible
			for (int z = WallSX1; z < WallSX2; ++z)
			{
				if (floorclip[z] > ceilingclip[z])
				{
					doorunderwater = true;
					r_fakingunderwater = true;
					break;
				}
			}
		}
	}
#endif

	if (underwater || doorunderwater)
	{
		ff->floorplane.normal = sec->floor.normal;
		ff->floorplane.dist = sec->floor.dist;
		ff->ceilplane.normal = -s->floor.normal;
		ff->ceilplane.dist = -s->floor.dist/* + 1*/;
//		ff->ColourMap = s->ColourMap;
		ff->params.Fade = s->params.Fade;
	}

	// killough 11/98: prevent sudden light changes from non-water sectors:
	if ((underwater/* && !back*/) || doorunderwater)
	{
		// head-below-floor hack
		ff->floorplane.pic			= diffTex ? sec->floor.pic : s->floor.pic;
		ff->floorplane.xoffs		= s->floor.xoffs;
		ff->floorplane.yoffs		= s->floor.yoffs;
		ff->floorplane.XScale		= s->floor.XScale;
		ff->floorplane.YScale		= s->floor.YScale;
		ff->floorplane.Angle		= s->floor.Angle;
		ff->floorplane.BaseAngle	= s->floor.BaseAngle;
		ff->floorplane.BaseYOffs	= s->floor.BaseYOffs;

		ff->ceilplane.normal		= -s->floor.normal;
		ff->ceilplane.dist			= -s->floor.dist/* + 1*/;
		if (s->ceiling.pic == skyflatnum)
		{
			ff->floorplane.normal	= -ff->ceilplane.normal;
			ff->floorplane.dist		= -ff->ceilplane.dist/* + 1*/;
			ff->ceilplane.pic		= ff->floorplane.pic;
			ff->ceilplane.xoffs		= ff->floorplane.xoffs;
			ff->ceilplane.yoffs		= ff->floorplane.yoffs;
			ff->ceilplane.XScale	= ff->floorplane.XScale;
			ff->ceilplane.YScale	= ff->floorplane.YScale;
			ff->ceilplane.Angle		= ff->floorplane.Angle;
			ff->ceilplane.BaseAngle	= ff->floorplane.BaseAngle;
			ff->ceilplane.BaseYOffs	= ff->floorplane.BaseYOffs;
		}
		else
		{
			ff->ceilplane.pic		= diffTex ? s->floor.pic : s->ceiling.pic;
			ff->ceilplane.xoffs		= s->ceiling.xoffs;
			ff->ceilplane.yoffs		= s->ceiling.yoffs;
			ff->ceilplane.XScale	= s->ceiling.XScale;
			ff->ceilplane.YScale	= s->ceiling.YScale;
			ff->ceilplane.Angle		= s->ceiling.Angle;
			ff->ceilplane.BaseAngle	= s->ceiling.BaseAngle;
			ff->ceilplane.BaseYOffs	= s->ceiling.BaseYOffs;
		}

		if (!(s->SectorFlags & sector_t::SF_NoFakeLight))
		{
			ff->params.lightlevel = s->params.lightlevel;
			ff->params.LightColour = s->params.LightColour;

/*			if (floorlightlevel != NULL)
			{
				*floorlightlevel = GetFloorLight (s);
			}

			if (ceilinglightlevel != NULL)
			{
				*ceilinglightlevel = GetFloorLight (s);
			}*/
		}
	}
	else if (heightsec && orgceilz > refceilz &&
		!(s->SectorFlags & sector_t::SF_FakeFloorOnly) &&
		vieworg.z >= heightsec->ceiling.GetPointZ(vieworg))
	{
		// Above-ceiling hack
		ff->ceilplane.normal	= s->ceiling.normal;
		ff->ceilplane.dist		= s->ceiling.dist;
		ff->floorplane.normal	= -s->ceiling.normal;
		ff->floorplane.dist		= -s->ceiling.dist/* + 1*/;
		ff->params.Fade 		= s->params.Fade;
//		ff->params.ColourMap	= s->params.ColourMap;

		ff->ceilplane.pic = diffTex ? sec->ceiling.pic : s->ceiling.pic;
		ff->floorplane.pic									= s->ceiling.pic;
		ff->floorplane.xoffs	= ff->ceilplane.xoffs		= s->ceiling.xoffs;
		ff->floorplane.yoffs	= ff->ceilplane.yoffs		= s->ceiling.yoffs;
		ff->floorplane.XScale	= ff->ceilplane.XScale		= s->ceiling.XScale;
		ff->floorplane.YScale	= ff->ceilplane.YScale		= s->ceiling.YScale;
		ff->floorplane.Angle	= ff->ceilplane.Angle		= s->ceiling.Angle;
		ff->floorplane.BaseAngle	= ff->ceilplane.BaseAngle	= s->ceiling.BaseAngle;
		ff->floorplane.BaseYOffs	= ff->ceilplane.BaseYOffs	= s->ceiling.BaseYOffs;

		if (s->floor.pic != skyflatnum)
		{
			ff->ceilplane.normal	= sec->ceiling.normal;
			ff->ceilplane.dist		= sec->ceiling.dist;
			ff->floorplane.pic		= s->floor.pic;
			ff->floorplane.xoffs	= s->floor.xoffs;
			ff->floorplane.yoffs	= s->floor.yoffs;
			ff->floorplane.XScale	= s->floor.XScale;
			ff->floorplane.YScale	= s->floor.YScale;
			ff->floorplane.Angle	= s->floor.Angle;
		}

		if (!(s->SectorFlags & sector_t::SF_NoFakeLight))
		{
			ff->params.lightlevel  = s->params.lightlevel;
			ff->params.LightColour = s->params.LightColour;

/*			if (floorlightlevel != NULL)
			{
				*floorlightlevel = GetFloorLight (s);
			}

			if (ceilinglightlevel != NULL)
			{
				*ceilinglightlevel = GetCeilingLight (s);
			}*/
		}
	}
	unguard;
}

//==========================================================================
//
//	VRenderLevel::UpdateWorld
//
//==========================================================================

void VRenderLevel::UpdateWorld(const refdef_t* rd, const VViewClipper* Range)
{
	guard(VRenderLevel::UpdateWorld);
	float	dummy_bbox[6] = {-99999, -99999, -99999, 99999, 99999, 99999};

	ViewClip.ClearClipNodes(vieworg, Level);
	ViewClip.ClipInitFrustrumRange(viewangles, viewforward, viewright, viewup,
		rd->fovx, rd->fovy);
	if (Range)
	{
		//	Range contains a valid range, so we must clip away holes in it.
		ViewClip.ClipToRanges(*Range);
	}

	//	Update fake sectors.
	for (int i = 0; i < Level->NumSectors; i++)
	{
		if (Level->Sectors[i].heightsec &&
			!(Level->Sectors[i].heightsec->SectorFlags & sector_t::SF_IgnoreHeightSec))
		{
			UpdateFakeFlats(&Level->Sectors[i]);
		}
	}

	UpdateBSPNode(Level->NumNodes - 1, dummy_bbox);	// head node is the last node output
	unguard;
}

//==========================================================================
//
//	VAdvancedRenderLevel::UpdateWorld
//
//==========================================================================

void VAdvancedRenderLevel::UpdateWorld()
{
	guard(VAdvancedRenderLevel::UpdateWorld);
	float	dummy_bbox[6] = {-99999, -99999, -99999, 99999, 99999, 99999};

	//	Update fake sectors.
	for (int i = 0; i < Level->NumSectors; i++)
	{
		if (Level->Sectors[i].heightsec &&
			!(Level->Sectors[i].heightsec->SectorFlags & sector_t::SF_IgnoreHeightSec))
		{
			UpdateFakeFlats(&Level->Sectors[i]);
		}
	}

	UpdateBSPNode(Level->NumNodes - 1, dummy_bbox);	// head node is the last node output
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::SetupFakeFloors
//
//==========================================================================

void VRenderLevelShared::SetupFakeFloors(sector_t* Sec)
{
	guard(VRenderLevelShared::SetupFakeFloors);
	sector_t* HeightSec = Sec->heightsec;

	if (HeightSec->SectorFlags & sector_t::SF_IgnoreHeightSec)
	{
		return;
	}

	Sec->fakefloors = new fakefloor_t;
	memset(Sec->fakefloors, 0, sizeof(fakefloor_t));
	Sec->fakefloors->floorplane = Sec->floor;
	Sec->fakefloors->ceilplane = Sec->ceiling;
	Sec->fakefloors->params = Sec->params;

	Sec->topregion->params = &Sec->fakefloors->params;
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::FreeSurfaces
//
//==========================================================================

void VRenderLevelShared::FreeSurfaces(surface_t* InSurf)
{
	guard(VRenderLevelShared::FreeSurfaces);
	surface_t* next;
	for (surface_t* s = InSurf; s; s = next)
	{
		if (s->CacheSurf)
		{
			FreeSurfCache(s->CacheSurf);
		}
		if (s->lightmap)
		{
			Z_Free(s->lightmap);
		}
		if (s->lightmap_rgb)
		{
			Z_Free(s->lightmap_rgb);
		}
		next = s->next;
		Z_Free(s);
	}
	unguard;
}

//==========================================================================
//
//	VRenderLevelShared::FreeSegParts
//
//==========================================================================

void VRenderLevelShared::FreeSegParts(segpart_t* ASP)
{
	guard(VRenderLevelShared::FreeSegParts);
	for (segpart_t* sp = ASP; sp; sp = sp->next)
	{
		FreeWSurfs(sp->surfs);
	}
	unguard;
}
