raze/source/core/rendering/scene/hw_walls.cpp
Christoph Oelckers 49f87e6227 - got rid of gotpic.
Now everything uses the 'seen' flag in the texture which indicates that the texture was actually used for hardware rendering since the last check.
It's also only the 3 places checking this flag which are responsible for clearing it.
2022-12-11 19:43:58 +01:00

1264 lines
35 KiB
C++
Raw Blame History

//
//---------------------------------------------------------------------------
//
// Copyright(C) 2000-2016 Christoph Oelckers
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
//
#include "texturemanager.h"
#include "hw_dynlightdata.h"
#include "hw_material.h"
#include "hw_cvars.h"
#include "hw_clock.h"
//#include "hw_lighting.h"
#include "hw_drawinfo.h"
#include "hw_portal.h"
#include "hw_lightbuffer.h"
#include "hw_renderstate.h"
#include "hw_skydome.h"
#include "hw_drawstructs.h"
#include "hw_vertexmap.h"
#include "gamefuncs.h"
#include "cmdlib.h"
#include "v_video.h"
#include "flatvertices.h"
DCoreActor* wall_to_sprite_actors[8]; // gets updated each frame. Todo: Encapsulate this better without having to permanently store actors in the wall object.
//==========================================================================
//
//
//
//==========================================================================
static walltype* IsOnWall(tspritetype* tspr, int height, DVector2& outpos)
{
double maxorthdist = 3 * maptoworld; // maximum orthogonal distance to be considered an attached sprite.
double maxdistsq = maxorthdist * maxorthdist;
walltype* best = nullptr;
auto sect = tspr->sectp;
double tx = tspr->pos.X;
double ty = tspr->pos.Y;
for(auto& wal : sect->walls)
{
// Intentionally include two sided walls. Even on them the sprite should be projected onto the wall for better results.
auto d = wal.delta();
auto deltaang = absangle(d.Angle(), tspr->Angles.Yaw);
const DAngle maxangdelta = DAngle360 / 1024;
// angle of the sprite must either be the wall's normal or the negative wall's normal to be aligned.
if (deltaang >= DAngle90 - maxangdelta && deltaang <= DAngle90 + maxangdelta)
{
if (!((tspr->Angles.Yaw.Buildang()) & 510))
{
// orthogonal lines do not check the actual position so that certain off-sector sprites get handled properly.
// In Wanton Destruction's airplane level there's such a sprite assigned to the wrong sector.
if (d.X == 0)
{
double newdist = fabs(tx - wal.pos.X);
if (newdist < maxorthdist)
{
maxorthdist = newdist;
best = &wal;
}
}
else if (d.Y == 0)
{
double newdist = fabs(ty - wal.pos.Y);
if (newdist < maxorthdist)
{
maxorthdist = newdist;
best = &wal;
}
}
}
else
{
double wdist = SquareDistToWall(tx, ty, &wal, &outpos);
if (wdist <= maxdistsq)
{
return &wal;
}
}
}
}
return best;
}
//==========================================================================
//
// General purpose wall rendering function
// everything goes through here
//
//==========================================================================
void HWWall::RenderWall(HWDrawInfo *di, FRenderState &state, int textured)
{
assert(vertcount > 0);
state.SetLightIndex(dynlightindex);
state.Draw(DT_TriangleFan, vertindex, vertcount);
vertexcount += vertcount;
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::RenderFogBoundary(HWDrawInfo *di, FRenderState &state)
{
if (gl_fogmode)// && !di->isFullbrightScene())
{
state.EnableDrawBufferAttachments(false);
SetLightAndFog(di, state, fade, palette, shade, visibility, alpha);
state.SetEffect(EFF_FOGBOUNDARY);
state.AlphaFunc(Alpha_GEqual, 0.f);
state.SetDepthBias(-1, -128);
RenderWall(di, state, HWWall::RWF_BLANK);
state.ClearDepthBias();
state.SetEffect(EFF_NONE);
state.EnableDrawBufferAttachments(true);
}
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::RenderMirrorSurface(HWDrawInfo *di, FRenderState &state)
{
if (!TexMan.mirrorTexture.isValid()) return;
state.SetDepthFunc(DF_LEqual);
// we use texture coordinates and texture matrix to pass the normal stuff to the shader so that the default vertex buffer format can be used as is.
state.EnableTextureMatrix(true);
// Use sphere mapping for this
state.SetEffect(EFF_SPHEREMAP);
SetLightAndFog(di, state, fade, palette, min<int>(shade, numshades), visibility, alpha);
//state.SetColor(PalEntry(25, globalr >> 1, globalg >> 1, globalb >> 1));
state.SetColor(PalEntry(25, 128, 128, 128));
state.SetRenderStyle(STYLE_Add);
state.AlphaFunc(Alpha_Greater, 0);
auto tex = TexMan.GetGameTexture(TexMan.mirrorTexture, false);
state.SetMaterial(tex, UF_None, 0, CLAMP_NONE, 0, -1); // do not upscale the mirror texture.
RenderWall(di, state, HWWall::RWF_BLANK);
state.EnableTextureMatrix(false);
state.SetEffect(EFF_NONE);
state.AlphaFunc(Alpha_GEqual, 0.5f);
state.SetDepthFunc(DF_Less);
state.SetRenderStyle(STYLE_Translucent);
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::RenderTexturedWall(HWDrawInfo *di, FRenderState &state, int rflags)
{
SetLightAndFog(di, state, fade, palette, shade, visibility, alpha);
state.SetMaterial(texture, UF_Texture, 0, (flags & (HWF_CLAMPX | HWF_CLAMPY)), TRANSLATION(Translation_Remap + curbasepal, palette), -1);
if (Sprite == nullptr)
{
if (shade > numshades && (GlobalMapFog || (fade & 0xffffff)))
{
state.SetObjectColor(fade | 0xff000000);
state.EnableTexture(false);
}
if (!(seg->cstat & CSTAT_WALL_ROTATE_90))
{
int h = (int)texture->GetDisplayHeight();
int h2 = 1 << sizeToBits(h);
if (h2 < h) h2 *= 2;
if (h != h2)
{
float xOffset = 1.f / texture->GetDisplayWidth();
state.SetNpotEmulation(float(h2) / h, xOffset);
}
}
RenderWall(di, state, rflags);
}
else if (!(rflags & RWF_TRANS))
{
auto oldbias = state.GetDepthBias();
if (walldist) state.SetDepthBias(-1, -129);
else state.ClearDepthBias();
RenderWall(di, state, rflags);
state.SetDepthBias(oldbias);
}
else
RenderWall(di, state, rflags);
state.SetNpotEmulation(0.f, 0.f);
state.SetObjectColor(0xffffffff);
state.EnableTexture(true);
/* none of these functions is in use.
state.SetObjectColor2(0);
state.SetAddColor(0);
state.SetTextureMode(tmode);
state.EnableGlow(false);
state.EnableGradient(false);
state.ApplyTextureManipulation(nullptr);
*/
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::RenderTranslucentWall(HWDrawInfo *di, FRenderState &state)
{
if (RenderStyle.BlendOp != STYLEOP_Add)
{
state.EnableBrightmap(false);
}
state.SetRenderStyle(RenderStyle);
state.SetTextureMode(RenderStyle);
if (texture && !checkTranslucentReplacement(texture->GetID(), palette)) state.AlphaFunc(Alpha_GEqual, texture->alphaThreshold);
else state.AlphaFunc(Alpha_GEqual, 0.f);
RenderTexturedWall(di, state, HWWall::RWF_TEXTURED);
state.SetRenderStyle(STYLE_Translucent);
state.EnableBrightmap(true);
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::DrawWall(HWDrawInfo *di, FRenderState &state, bool translucent)
{
rendered_lines++;
if (screen->BuffersArePersistent())
{
/*
if (di->Level->HasDynamicLights && !di->isFullbrightScene() && texture != nullptr)
{
SetupLights(di, lightdata);
}
*/
MakeVertices(di, !!(flags & HWWall::HWF_TRANSLUCENT));
}
state.SetNormal(glseg.Normal());
if (!translucent)
{
RenderTexturedWall(di, state, HWWall::RWF_TEXTURED);
}
else
{
switch (type)
{
case RENDERWALL_MIRRORSURFACE:
RenderMirrorSurface(di, state);
break;
case RENDERWALL_FOGBOUNDARY:
RenderFogBoundary(di, state);
break;
default:
RenderTranslucentWall(di, state);
break;
}
}
}
//==========================================================================
//
// Collect lights for shader
//
//==========================================================================
#if 0
void HWWall::SetupLights(HWDrawInfo *di, FDynLightData &lightdata)
{
lightdata.Clear();
if (RenderStyle == STYLE_Add && !di->Level->lightadditivesurfaces) return; // no lights on additively blended surfaces.
// check for wall types which cannot have dynamic lights on them (portal types never get here so they don't need to be checked.)
switch (type)
{
case RENDERWALL_FOGBOUNDARY:
case RENDERWALL_MIRRORSURFACE:
case RENDERWALL_COLOR:
return;
}
float vtx[]={glseg.x1,zbottom[0],glseg.y1, glseg.x1,ztop[0],glseg.y1, glseg.x2,ztop[1],glseg.y2, glseg.x2,zbottom[1],glseg.y2};
Plane p;
auto normal = glseg.Normal();
p.Set(normal, -normal.X * glseg.x1 - normal.Z * glseg.y1);
FLightNode *node;
if (seg->sidedef == NULL)
{
node = NULL;
}
else if (!(seg->sidedef->Flags & WALLF_POLYOBJ))
{
node = seg->sidedef->lighthead;
}
else if (sub)
{
// Polobject segs cannot be checked per sidedef so use the subsector instead.
node = sub->section->lighthead;
}
else node = NULL;
// Iterate through all dynamic lights which touch this wall and render them
while (node)
{
if (node->lightsource->IsActive())
{
iter_dlight++;
DVector3 posrel = node->lightsource->PosRelative(seg->frontsector->PortalGroup);
float x = posrel.X;
float y = posrel.Y;
float z = posrel.Z;
float dist = fabsf(p.DistToPoint(x, z, y));
float radius = node->lightsource->GetRadius();
float scale = 1.0f / ((2.f * radius) - dist);
FVector3 fn, pos;
if (radius > 0.f && dist < radius)
{
FVector3 nearPt, up, right;
pos = { x, z, y };
fn = p.Normal();
fn.GetRightUp(right, up);
FVector3 tmpVec = fn * dist;
nearPt = pos + tmpVec;
FVector3 t1;
int outcnt[4]={0,0,0,0};
texcoord tcs[4];
// do a quick check whether the light touches this polygon
for(int i=0;i<4;i++)
{
t1 = FVector3(&vtx[i*3]);
FVector3 nearToVert = t1 - nearPt;
tcs[i].u = ((nearToVert | right) * scale) + 0.5f;
tcs[i].v = ((nearToVert | up) * scale) + 0.5f;
if (tcs[i].u<0) outcnt[0]++;
if (tcs[i].u>1) outcnt[1]++;
if (tcs[i].v<0) outcnt[2]++;
if (tcs[i].v>1) outcnt[3]++;
}
if (outcnt[0]!=4 && outcnt[1]!=4 && outcnt[2]!=4 && outcnt[3]!=4)
{
draw_dlight += GetLight(lightdata, seg->frontsector->PortalGroup, p, node->lightsource, true);
}
}
}
node = node->nextLight;
}
dynlightindex = screen->mLights->UploadLights(lightdata);
}
#endif
//==========================================================================
//
//
//
//==========================================================================
void HWWall::PutWall(HWDrawInfo *di, bool translucent)
{
if (translucent || (texture && texture->GetTranslucency() && type == RENDERWALL_M2S))
{
flags |= HWF_TRANSLUCENT;
ViewDistance = (di->Viewpoint.Pos.XY() - DVector2((glseg.x1 + glseg.x2) * 0.5f, (glseg.y1 + glseg.y2) * 0.5f)).LengthSquared();
}
if (seg && (seg->cstat & CSTAT_WALL_ROTATE_90))
{
float f;
// rotate 90<39> clockwise. The coordinates have already been set up in rotated space, so all we need to do here is swap u and v and then negate the new v.
f = tcs[UPLFT].u; tcs[UPLFT].u = tcs[UPLFT].v; tcs[UPLFT].v = 1.f - f;
f = tcs[LOLFT].u; tcs[LOLFT].u = tcs[LOLFT].v; tcs[LOLFT].v = 1.f - f;
f = tcs[UPRGT].u; tcs[UPRGT].u = tcs[UPRGT].v; tcs[UPRGT].v = 1.f - f;
f = tcs[LORGT].u; tcs[LORGT].u = tcs[LORGT].v; tcs[LORGT].v = 1.f - f;
}
if (texture->isHardwareCanvas())
{
tcs[UPLFT].v = 1.f - tcs[UPLFT].v;
tcs[LOLFT].v = 1.f - tcs[LOLFT].v;
tcs[UPRGT].v = 1.f - tcs[UPRGT].v;
tcs[LORGT].v = 1.f - tcs[LORGT].v;
}
if (!screen->BuffersArePersistent())
{
/*
if (di->Level->HasDynamicLights && !di->isFullbrightScene() && texture != nullptr)
{
SetupLights(di, lightdata);
}
*/
MakeVertices(di, translucent);
}
di->AddWall(this);
// make sure that following parts of the same linedef do not get this one's vertex and lighting info.
vertcount = 0;
dynlightindex = -1;
flags &= ~(HWF_TRANSLUCENT|HWF_CLAMPX|HWF_CLAMPY);
}
//==========================================================================
//
// will be done later.
//
//==========================================================================
void HWWall::PutPortal(HWDrawInfo *di, int ptype, int plane)
{
HWPortal * portal = nullptr;
MakeVertices(di, false);
switch (ptype)
{
#if 0
// portals don't go into the draw list.
// Instead they are added to the portal manager
case PORTALTYPE_HORIZON:
horizon = portalState.UniqueHorizons.Get(horizon);
portal = di->FindPortal(horizon);
if (!portal)
{
portal = new HWHorizonPortal(&portalState, horizon, di->Viewpoint);
di->Portals.Push(portal);
}
portal->AddLine(this);
break;
case PORTALTYPE_SKYBOX:
portal = di->FindPortal(secportal);
if (!portal)
{
// either a regular skybox or an Eternity-style horizon
if (secportal->mType != PORTS_SKYVIEWPOINT) portal = new HWEEHorizonPortal(&portalState, secportal);
else
{
portal = new HWSkyboxPortal(&portalState, secportal);
di->Portals.Push(portal);
}
}
portal->AddLine(this);
break;
#endif
case PORTALTYPE_SECTORSTACK:
portal = di->FindPortal(this->portal);
if (!portal)
{
portal = new HWSectorStackPortal(&portalState, this->portal);
di->Portals.Push(portal);
}
portal->AddLine(this);
break;
#if 0
case PORTALTYPE_PLANEMIRROR:
if (portalState.PlaneMirrorMode * planemirror->fC() <= 0)
{
planemirror = portalState.UniquePlaneMirrors.Get(planemirror);
portal = di->FindPortal(planemirror);
if (!portal)
{
portal = new HWPlaneMirrorPortal(&portalState, planemirror);
di->Portals.Push(portal);
}
portal->AddLine(this);
}
break;
#endif
case PORTALTYPE_MIRROR:
// These are unique. No need to look existing ones up.
portal = new HWMirrorPortal(&portalState, seg);
di->Portals.Push(portal);
portal->AddLine(this);
if (gl_mirror_envmap)
{
// draw a reflective layer over the mirror
di->AddMirrorSurface(this);
}
break;
case PORTALTYPE_LINETOLINE:
// These are also unique.
portal = new HWLineToLinePortal(&portalState, seg, &wall[seg->portalnum]);
di->Portals.Push(portal);
portal->AddLine(this);
break;
case PORTALTYPE_LINETOSPRITE:
// These are also unique.
assert(seg->portalnum >= 0 && seg->portalnum < 8);
portal = new HWLineToSpritePortal(&portalState, seg, wall_to_sprite_actors[seg->portalnum]);
di->Portals.Push(portal);
portal->AddLine(this);
break;
case PORTALTYPE_SKY:
sky = portalState.UniqueSkies.Get(sky);
portal = di->FindPortal(sky);
if (!portal)
{
portal = new HWSkyPortal(screen->mSkyData, &portalState, sky);
di->Portals.Push(portal);
}
portal->AddLine(this);
break;
}
vertcount = 0;
if (plane != -1 && portal)
{
portal->planesused |= (1<<plane);
}
}
//==========================================================================
//
// Build does not have horizon effects.
//
//==========================================================================
bool HWWall::DoHorizon(HWDrawInfo* di, walltype* seg, sectortype* fs, DVector2& v1, DVector2& v2)
{
#if 0
HWHorizonInfo hi;
lightlist_t * light;
// ZDoom doesn't support slopes in a horizon sector so I won't either!
ztop[1] = ztop[0] = fs->GetPlaneTexZ(sector_t::ceiling);
zbottom[1] = zbottom[0] = fs->GetPlaneTexZ(sector_t::floor);
auto vpz = di->Viewpoint.Pos.Z;
if (vpz < fs->GetPlaneTexZ(sector_t::ceiling))
{
if (vpz > fs->GetPlaneTexZ(sector_t::floor))
zbottom[1] = zbottom[0] = vpz;
if (fs->GetTexture(sector_t::ceiling) == skyflatnum)
{
SkyPlane(di, fs, sector_t::ceiling, false);
}
else
{
horizon = &hi;
PutPortal(di, PORTALTYPE_HORIZON, -1);
}
ztop[1] = ztop[0] = zbottom[0];
}
if (vpz > fs->GetPlaneTexZ(sector_t::floor))
{
zbottom[1] = zbottom[0] = fs->GetPlaneTexZ(sector_t::floor);
if (fs->GetTexture(sector_t::floor) == skyflatnum)
{
SkyPlane(di, fs, sector_t::floor, false);
}
else
{
horizon = &hi;
PutPortal(di, PORTALTYPE_HORIZON, -1);
}
}
#endif
return true;
}
//==========================================================================
//
//
//
//==========================================================================
bool HWWall::SetWallCoordinates(walltype * seg, float topleft, float topright, float bottomleft, float bottomright)
{
//
//
// set up coordinates for the left side of the polygon
//
// check left side for intersections
if (topleft >= bottomleft)
{
// normal case
ztop[0] = topleft;
zbottom[0] = bottomleft;
}
else
{
// ceiling below floor - clip to the visible part of the wall
float dch = topright - topleft;
float dfh = bottomright - bottomleft;
float inter_x = (bottomleft - topleft) / (dch - dfh);
float inter_y = topleft + inter_x * dch;
glseg.x1 = glseg.x1 + inter_x * (glseg.x2 - glseg.x1);
glseg.y1 = glseg.y1 + inter_x * (glseg.y2 - glseg.y1);
glseg.fracleft = inter_x;
zbottom[0] = ztop[0] = inter_y;
}
//
//
// set up coordinates for the right side of the polygon
//
// check left side for intersections
if (topright >= bottomright)
{
// normal case
ztop[1] = topright;
zbottom[1] = bottomright;
}
else
{
// ceiling below floor - clip to the visible part of the wall
float dch = topright - topleft;
float dfh = bottomright - bottomleft;
float inter_x = (bottomleft - topleft) / (dch - dfh);
float inter_y = topleft + inter_x * dch;
glseg.x2 = glseg.x1 + inter_x * (glseg.x2 - glseg.x1);
glseg.y2 = glseg.y1 + inter_x * (glseg.y2 - glseg.y1);
glseg.fracright = inter_x;
zbottom[1] = ztop[1] = inter_y;
}
return true;
}
//==========================================================================
//
// Do some tweaks with the texture coordinates to reduce visual glitches
//
//==========================================================================
void HWWall::CheckTexturePosition()
{
float sub;
if (texture->isHardwareCanvas()) return;
// clamp texture coordinates to a reasonable range.
// Extremely large values can cause visual problems
if (tcs[UPLFT].v < tcs[LOLFT].v || tcs[UPRGT].v < tcs[LORGT].v)
{
if (tcs[UPLFT].v < tcs[UPRGT].v)
{
sub = float(xs_FloorToInt(tcs[UPLFT].v));
}
else
{
sub = float(xs_FloorToInt(tcs[UPRGT].v));
}
tcs[UPLFT].v -= sub;
tcs[UPRGT].v -= sub;
tcs[LOLFT].v -= sub;
tcs[LORGT].v -= sub;
}
else
{
if (tcs[LOLFT].v < tcs[LORGT].v)
{
sub = float(xs_FloorToInt(tcs[LOLFT].v));
}
else
{
sub = float(xs_FloorToInt(tcs[LORGT].v));
}
tcs[UPLFT].v -= sub;
tcs[UPRGT].v -= sub;
tcs[LOLFT].v -= sub;
tcs[LORGT].v -= sub;
}
if (tcs[UPLFT].u >= 0.f && tcs[UPRGT].u >= 0.f && tcs[LOLFT].u >= 0.f && tcs[LORGT].u >= 0.f &&
tcs[UPLFT].u <= 1.f && tcs[UPRGT].u <= 1.f && tcs[LOLFT].u <= 1.f && tcs[LORGT].u <= 1.f)
{
flags |= HWF_CLAMPX;
}
if (tcs[UPLFT].v >= 0.f && tcs[UPRGT].v >= 0.f && tcs[LOLFT].v >= 0.f && tcs[LORGT].v >= 0.f &&
tcs[UPLFT].v <= 1.f && tcs[UPRGT].v <= 1.f && tcs[LOLFT].v <= 1.f && tcs[LORGT].v <= 1.f)
{
flags |= HWF_CLAMPY;
}
}
//==========================================================================
//
// Common part of wall drawers
//
//==========================================================================
void HWWall::DoTexture(HWDrawInfo* di, walltype* wal, walltype* refwall, float refheight, float topleft, float topright, float bottomleft, float bottomright)
{
auto glsave = glseg;
SetWallCoordinates(wal, topleft, topright, bottomleft, bottomright);
bool xflipped = (wal->cstat & CSTAT_WALL_XFLIP);
float leftdist = xflipped ? 1.f - glseg.fracleft : glseg.fracleft;
float rightdist = xflipped ? 1.f - glseg.fracright : glseg.fracright;
float tw = texture->GetDisplayWidth();
float th = texture->GetDisplayHeight();
if (wal->cstat & CSTAT_WALL_ROTATE_90) std::swap(tw, th);
int pow2size = 1 << sizeToBits(th);
if (pow2size < th) pow2size *= 2;
float ypanning = refwall->ypan_ ? pow2size * refwall->ypan_ / (256.0f * th) : 0;
tcs[LOLFT].u = tcs[UPLFT].u = ((leftdist * 8.f * wal->xrepeat) + refwall->xpan_) / tw;
tcs[LORGT].u = tcs[UPRGT].u = ((rightdist * 8.f * wal->xrepeat) + refwall->xpan_) / tw;
auto setv = [=](float hl, float hr, float frac) -> float
{
float h = hl + (hr - hl) * frac;
h = (-(float)((refheight + h) * 256) / ((th * 2048.0f) / (float)(wal->yrepeat))) + ypanning;
if (refwall->cstat & CSTAT_WALL_YFLIP) h = -h;
return h;
};
tcs[UPLFT].v = setv(topleft, topright, glseg.fracleft);
tcs[LOLFT].v = setv(bottomleft, bottomright, glseg.fracleft);
tcs[UPRGT].v = setv(topleft, topright, glseg.fracright);
tcs[LORGT].v = setv(bottomleft, bottomright, glseg.fracright);
if (th == pow2size) CheckTexturePosition(); // for NPOT textures this adjustment can break things.
bool trans = type == RENDERWALL_M2S && maskWallHasTranslucency(wal);
if (trans)
{
RenderStyle = GetRenderStyle(0, !!(wal->cstat & CSTAT_WALL_TRANS_FLIP));
alpha = GetAlphaFromBlend((wal->cstat & CSTAT_WALL_TRANS_FLIP) ? DAMETH_TRANS2 : DAMETH_TRANS1, 0);
}
PutWall(di, trans);
flags = 0;
glseg = glsave;
}
//==========================================================================
//
// Handle one sided walls
//
//==========================================================================
void HWWall::DoOneSidedTexture(HWDrawInfo* di, walltype* wal, sectortype* frontsector, sectortype* backsector,
float topleft, float topright, float bottomleft, float bottomright)
{
// get the alignment reference position.
float refheight;
if ((wal->cstat & CSTAT_WALL_1WAY) && backsector)
{
if ((!(wal->cstat & CSTAT_WALL_BOTTOM_SWAP) && (wal->cstat & CSTAT_WALL_1WAY)) ||
((wal->cstat & CSTAT_WALL_BOTTOM_SWAP) && (wal->nextWall()->cstat & CSTAT_WALL_ALIGN_BOTTOM)))
refheight = frontsector->ceilingz;
else
refheight = backsector->floorz;
}
else
{
refheight = (wal->cstat & CSTAT_WALL_ALIGN_BOTTOM) ? frontsector->floorz : frontsector->ceilingz;
}
type = RENDERWALL_M1S;
DoTexture(di, wal, wal, refheight, topleft, topright, bottomleft, bottomright);
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::DoUpperTexture(HWDrawInfo* di, walltype* wal, sectortype* frontsector, sectortype* backsector,
float topleft, float topright, float bottomleft, float bottomright)
{
// get the alignment reference position.
float refheight = (wal->cstat & CSTAT_WALL_ALIGN_BOTTOM) ? frontsector->ceilingz : backsector->ceilingz;
type = RENDERWALL_TOP;
DoTexture(di, wal, wal, refheight, topleft, topright, bottomleft, bottomright);
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::DoLowerTexture(HWDrawInfo* di, walltype* wal, sectortype* frontsector, sectortype* backsector,
float topleft, float topright, float bottomleft, float bottomright)
{
// get the alignment reference position.
auto refwall = (wal->cstat & CSTAT_WALL_BOTTOM_SWAP) ? wal->nextWall() : wal;
float refheight = (refwall->cstat & CSTAT_WALL_ALIGN_BOTTOM) ? frontsector->ceilingz : backsector->floorz;
shade = refwall->shade;
palette = refwall->pal;
type = RENDERWALL_BOTTOM;
DoTexture(di, wal, refwall, refheight, topleft, topright, bottomleft, bottomright);
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::DoMidTexture(HWDrawInfo* di, walltype* wal,
sectortype* front, sectortype* back,
float fch1, float fch2, float ffh1, float ffh2,
float bch1, float bch2, float bfh1, float bfh2)
{
float topleft,bottomleft,topright,bottomright;
float refheight;
const int swapit = (wal->cstat & CSTAT_WALL_ALIGN_BOTTOM);
if (wal->cstat & CSTAT_WALL_1WAY)
{
// 1-sided wall
refheight = swapit ? front->ceilingz : back->ceilingz;
}
else
{
// masked wall
if (swapit)
refheight = min(front->floorz, back->floorz);
else
refheight = max(front->ceilingz, back->ceilingz);
}
if ((bch1 - fch1) * (bch2 - fch2) >= 0)
{
topleft = min(bch1, fch1);
topright = min(bch2, fch2);
}
else
{
// Front ceiling slope obstructs part of the wall
topleft = bch1;
topright = bch2;
}
if ((bfh1 - ffh1) * (bfh2 - ffh2) >= 0)
{
bottomleft = max(bfh1, ffh1);
bottomright = max(bfh2, ffh2);
}
else
{
// Front floor slope obstructs part of the wall
bottomleft = bfh1;
bottomright = bfh2;
}
if (topleft<=bottomleft && topright<=bottomright) return;
type = seg->cstat & CSTAT_WALL_1WAY ? RENDERWALL_M1S : RENDERWALL_M2S;
// todo: transparency.
DoTexture(di, wal, wal, refheight, topleft, topright, bottomleft, bottomright);
RenderStyle = STYLE_Translucent;
alpha = 1.f;
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::Process(HWDrawInfo* di, walltype* wal, sectortype* frontsector, sectortype* backsector)
{
auto backwall = wal->twoSided()? wal->nextWall() : nullptr;
auto p2wall = wal->point2Wall();
float fch1;
float ffh1;
float fch2;
float ffh2;
FVector2 v1(wal->pos.X, -wal->pos.Y);
FVector2 v2(p2wall->pos.X, -p2wall->pos.Y);
PlanesAtPoint(frontsector, wal->pos.X, wal->pos.Y, &fch1, &ffh1);
PlanesAtPoint(frontsector, p2wall->pos.X, p2wall->pos.Y, &fch2, &ffh2);
#ifdef _DEBUG
if (wallindex(wal) == 34)
{
int a = 0;
}
#endif
this->seg = wal;
this->frontsector = frontsector;
this->backsector = backsector;
Sprite = nullptr;
vertindex = 0;
vertcount = 0;
glseg.x1 = v1.X;
glseg.y1 = v1.Y;
glseg.x2 = v2.X;
glseg.y2 = v2.Y;
glseg.fracleft = 0;
glseg.fracright = 1;
flags = 0;
dynlightindex = -1;
shade = wal->shade;
palette = wal->pal;
fade = lookups.getFade(wal->pal);
visibility = sectorVisibility(frontsector);
alpha = 1.0f;
RenderStyle = STYLE_Translucent;
texture = NULL;
if (gl_seamless)
{
auto v = &vertices[vertexMap[wallindex(wal)]];
if (v->dirty) v->RecalcVertexHeights();
v = &vertices[vertexMap[wal->point2]];
if (v->dirty) v->RecalcVertexHeights();
}
/*
if (wal->linedef->special == Line_Horizon)
{
SkyNormal(di, frontsector, v1, v2);
DoHorizon(di, wal, frontsector, v1, v2);
return;
}
*/
bool isportal = false;// wal->linedef->isVisualPortal() && wal->sidedef == wal->linedef->sidedef[0];
if (seg->portalflags)
{
int ptype = -1;
if (seg->portalflags == PORTAL_WALL_MIRROR) ptype = PORTALTYPE_MIRROR;
else if (seg->portalflags == PORTAL_WALL_VIEW) ptype = PORTALTYPE_LINETOLINE;
else if (seg->portalflags == PORTAL_WALL_TO_SPRITE) ptype = PORTALTYPE_LINETOSPRITE;
if (ptype != -1)
{
ztop[0] = fch1;
ztop[1] = fch2;
zbottom[0] = ffh1;
zbottom[1] = ffh2;
PutPortal(di, ptype, -1);
return;
}
}
if (!backsector || !backwall)
{
// sector's sky
SkyNormal(di, frontsector, v1, v2, fch1, fch2, ffh1, ffh2);
// normal texture
int tilenum = ((wal->cstat & CSTAT_WALL_1WAY) && wal->nextwall != -1) ? wal->overpicnum : wal->wallpicnum;
tileUpdatePicnum(&tilenum);
texture = tileGetTexture(tilenum);
if (texture && texture->isValid())
{
DoOneSidedTexture(di, wal, frontsector, backsector, fch1, fch2, ffh1, ffh2);
}
}
else // two sided
{
float bfh1;
float bfh2;
float bch1;
float bch2;
PlanesAtPoint(backsector, wal->pos.X, wal->pos.Y, &bch1, &bfh1);
PlanesAtPoint(backsector, p2wall->pos.X, p2wall->pos.Y, &bch2, &bfh2);
SkyTop(di, wal, frontsector, backsector, v1, v2, fch1, fch2);
SkyBottom(di, wal, frontsector, backsector, v1, v2, ffh1, ffh2);
// upper texture
if (!(frontsector->ceilingstat & backsector->ceilingstat & CSTAT_SECTOR_SKY))
{
float bch1a = bch1;
float bch2a = bch2;
if (ffh1 > bch1 || ffh2 > bch2)
{
// the back sector's floor obstructs part of this wall. Todo: Handle the portal case better.
if ((ffh1 > bch1 && ffh2 > bch2) || frontsector->portalflags == PORTAL_SECTOR_FLOOR)
{
bch2a = ffh2;
bch1a = ffh1;
}
}
if (bch1a < fch1 || bch2a < fch2)
{
int tilenum = wal->wallpicnum;
tileUpdatePicnum(&tilenum);
texture = tileGetTexture(tilenum);
if (texture && texture->isValid())
{
DoUpperTexture(di, wal, frontsector, backsector, fch1, fch2, bch1a, bch2a);
}
}
}
if (wal->cstat & (CSTAT_WALL_MASKED | CSTAT_WALL_1WAY))
{
int tilenum = wal->overpicnum;
tileUpdatePicnum(&tilenum);
texture = tileGetTexture(tilenum);
if (texture && texture->isValid())
{
DoMidTexture(di, wal, frontsector, backsector, fch1, fch2, ffh1, ffh2, bch1, bch2, bfh1, bfh2);
}
}
// lower texture
if (!(frontsector->floorstat & backsector->floorstat & CSTAT_SECTOR_SKY))
{
if (fch1 < bfh1 || fch2 < bfh2)
{
// the back sector's ceiling obstructs part of this wall. Todo: Handle the portal case better.
if ((fch1 < bfh1 && fch2 < bfh2) || frontsector->portalflags == PORTAL_SECTOR_CEILING)
{
bfh1 = fch1;
bfh2 = fch2;
}
}
if (bfh1 > ffh1 || bfh2 > ffh2)
{
auto w = (wal->cstat & CSTAT_WALL_BOTTOM_SWAP) ? backwall : wal;
int tilenum = w->wallpicnum;
tileUpdatePicnum(&tilenum);
texture = tileGetTexture(tilenum);
if (texture && texture->isValid())
{
DoLowerTexture(di, wal, frontsector, backsector, bfh1, bfh2, ffh1, ffh2);
}
}
}
}
}
//==========================================================================
//
// checks if the wall sprite has changed since the last call.
// Returns:
// 0 if identical or only render style changed
// 1 if vertical orientation changed
// 2 if horizontal orientation changed
// 3 if positioning changed
//
//==========================================================================
int HWWall::CheckWallSprite(tspritetype* spr, tspritetype* last)
{
// If the position changed we need to recalculate everything.
if (spr->pos.XY() != last->pos.XY() || spr->sectp != last->sectp || spr->Angles.Yaw != last->Angles.Yaw) return 3;
// if the horizontal orientation changes we need to recalculate the walls this attaches to, but not the positioning.
if (spr->scale.X != last->scale.X || spr->xoffset != last->xoffset || spr->picnum != last->picnum || ((spr->cstat ^ last->cstat) & CSTAT_SPRITE_XFLIP)) return 2;
// only y-positioning changed - we need to re-check the wall tiers this sprite attaches to
if(spr->scale.Y != last->scale.Y || spr->yoffset != last->yoffset || ((spr->cstat ^ last->cstat) & (CSTAT_SPRITE_YFLIP | CSTAT_SPRITE_YCENTER))) return 1;
// all remaining properties only affect the render style which is not relevant for positioning a wall sprite
return 0;
}
//==========================================================================
//
//
//
//==========================================================================
void HWWall::ProcessWallSprite(HWDrawInfo* di, tspritetype* spr, sectortype* sector)
{
auto tex = tileGetTexture(spr->picnum);
if (!tex || !tex->isValid()) return;
seg = nullptr;
Sprite = spr;
DVector2 pos[2];
GetWallSpritePosition(spr, spr->pos.XY(), pos, true);
glseg.x1 = pos[0].X;
glseg.y1 = -pos[0].Y;
glseg.x2 = pos[1].X;
glseg.y2 = -pos[1].Y;
if (spr->cstat & CSTAT_SPRITE_ONE_SIDE)
{
if (PointOnLineSide(di->Viewpoint.Pos.X, di->Viewpoint.Pos.Y, glseg.x1, glseg.y1, glseg.x2 - glseg.x1, glseg.y2 - glseg.y1) <= 0)
{
return;
}
}
vertindex = 0;
vertcount = 0;
type = RENDERWALL_M2S;
frontsector = sector;
backsector = sector;
texture = tex;
flags = HWF_CLAMPX|HWF_CLAMPY;
dynlightindex = -1;
shade = spr->shade;
palette = spr->pal;
fade = lookups.getFade(sector->floorpal); // fog is per sector.
visibility = sectorVisibility(sector);
SetSpriteTranslucency(Sprite, alpha, RenderStyle);
int height, topofs;
if (hw_hightile && TileFiles.tiledata[spr->picnum].hiofs.xsize)
{
height = TileFiles.tiledata[spr->picnum].hiofs.ysize;
topofs = (TileFiles.tiledata[spr->picnum].hiofs.yoffs + spr->yoffset);
}
else
{
height = (int)tex->GetDisplayHeight();
topofs = ((int)tex->GetDisplayTopOffset() + spr->yoffset);
}
DVector2 vec{};
walldist = IsOnWall(spr, height, vec);
if (walldist)
{
// project the sprite right onto the wall.
auto v1 = NearestPointOnWall(glseg.x1, -glseg.y1, walldist, false);
auto v2 = NearestPointOnWall(glseg.x2, -glseg.y2, walldist, false);
glseg.x1 = v1.X;
glseg.y1 = -v1.Y;
glseg.x2 = v2.X;
glseg.y2 = -v2.Y;
}
if (spr->cstat & CSTAT_SPRITE_YFLIP)
topofs = -topofs;
float yscale = spr->scale.Y;
float sprz = spr->pos.Z - topofs * yscale;
if (spr->cstat & CSTAT_SPRITE_YCENTER)
{
sprz += height * yscale * 0.5f;
if (height & 1) sprz += yscale * 0.5f; // Odd yspans
}
glseg.fracleft = 0;
glseg.fracright = 1;
tcs[LOLFT].u = tcs[UPLFT].u = (spr->cstat & CSTAT_SPRITE_XFLIP) ? 1.f : 0.f;
tcs[LORGT].u = tcs[UPRGT].u = (spr->cstat & CSTAT_SPRITE_XFLIP) ? 0.f : 1.f;
tcs[UPLFT].v = tcs[UPRGT].v = (spr->cstat & CSTAT_SPRITE_YFLIP) ? 1.f : 0.f;
tcs[LOLFT].v = tcs[LORGT].v = (spr->cstat & CSTAT_SPRITE_YFLIP) ? 0.f : 1.f;
zbottom[0] = zbottom[1] = -sprz;
ztop[0] = ztop[1] = -sprz + height * yscale;
if (zbottom[0] > ztop[0])
{
// reorder coordinates to make the clipping code below behave.
auto zz = zbottom[0];
zbottom[0] = zbottom[1] = ztop[0];
ztop[0] = ztop[1] = zz;
tcs[UPLFT].v = tcs[UPRGT].v = 1.f - tcs[UPLFT].v;
tcs[LOLFT].v = tcs[LORGT].v = 1.f - tcs[LOLFT].v;
}
// Clip sprites to ceilings/floors
if (!(sector->ceilingstat & CSTAT_SECTOR_SKY))
{
float polyh = (ztop[0] - zbottom[0]);
float ceilingz = -sector->ceilingz;
if (ceilingz < ztop[0] && ceilingz >= zbottom[0])
{
float newv = (ceilingz - zbottom[0]) / polyh;
tcs[UPLFT].v = tcs[UPRGT].v = tcs[LOLFT].v + newv * (tcs[UPLFT].v - tcs[LOLFT].v);
ztop[0] = ztop[1] = ceilingz;
}
}
if (!(sector->floorstat & CSTAT_SECTOR_SKY))
{
float polyh = (ztop[0] - zbottom[0]);
float floorz = -sector->floorz;
if (floorz <= ztop[0] && floorz > zbottom[0])
{
float newv = (floorz - zbottom[0]) / polyh;
tcs[LOLFT].v = tcs[LORGT].v = tcs[LOLFT].v + newv * (tcs[UPLFT].v - tcs[LOLFT].v);
zbottom[0] = zbottom[1] = floorz;
}
}
if (zbottom[0] >= ztop[0])
return; // nothing left to render.
// If the sprite is backward, flip it around so that we have guaranteed orientation when this is about to be sorted.
if (PointOnLineSide(di->Viewpoint.Pos.X, di->Viewpoint.Pos.Y, glseg.x1, glseg.y1, glseg.x2 - glseg.x1, glseg.y2 - glseg.y1 ) < 0)
{
std::swap(glseg.x1, glseg.x2);
std::swap(glseg.y1, glseg.y2);
// z is always the same on both sides.
std::swap(tcs[LOLFT], tcs[LORGT]);
std::swap(tcs[UPLFT], tcs[UPRGT]);
}
PutWall(di, spriteHasTranslucency(Sprite));
}