qzdoom/src/swrenderer/scene/r_opaque_pass.cpp
Christoph Oelckers e4d2380775 - moved all remaining fields from PClassActor to FActorInfo.
- added a few access functions for FActorInfo variables.

With PClassActor now empty the class descriptors can finally be converted back to static data outside the class hierarchy, like they were before the scripting merge, and untangle the game data from VM internals.
2017-04-12 00:07:41 +02:00

1036 lines
34 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// DESCRIPTION:
// BSP traversal, handling of LineSegs for rendering.
//
//-----------------------------------------------------------------------------
#include <stdlib.h>
#include "templates.h"
#include "doomdef.h"
#include "m_bbox.h"
#include "i_system.h"
#include "p_lnspec.h"
#include "p_setup.h"
#include "swrenderer/drawers/r_draw.h"
#include "swrenderer/plane/r_visibleplane.h"
#include "swrenderer/plane/r_visibleplanelist.h"
#include "swrenderer/things/r_sprite.h"
#include "swrenderer/things/r_wallsprite.h"
#include "swrenderer/things/r_voxel.h"
#include "swrenderer/things/r_particle.h"
#include "swrenderer/segments/r_clipsegment.h"
#include "swrenderer/line/r_wallsetup.h"
#include "swrenderer/scene/r_scene.h"
#include "swrenderer/scene/r_light.h"
#include "swrenderer/viewport/r_viewport.h"
#include "swrenderer/r_renderthread.h"
#include "r_3dfloors.h"
#include "r_portal.h"
#include "a_sharedglobal.h"
#include "g_level.h"
#include "p_effect.h"
#include "c_console.h"
#include "p_maputl.h"
// State.
#include "doomstat.h"
#include "r_state.h"
#include "r_opaque_pass.h"
#include "v_palette.h"
#include "r_sky.h"
#include "po_man.h"
#include "r_data/colormaps.h"
#include "g_levellocals.h"
EXTERN_CVAR(Bool, r_fullbrightignoresectorcolor);
EXTERN_CVAR(Bool, r_drawvoxels);
namespace swrenderer
{
RenderOpaquePass::RenderOpaquePass(RenderThread *thread) : renderline(thread)
{
Thread = thread;
}
sector_t *RenderOpaquePass::FakeFlat(sector_t *sec, sector_t *tempsec, int *floorlightlevel, int *ceilinglightlevel, seg_t *backline, int backx1, int backx2, double frontcz1, double frontcz2)
{
// 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.
// [RH] allow per-plane lighting
if (floorlightlevel != nullptr)
{
*floorlightlevel = sec->GetFloorLight();
}
if (ceilinglightlevel != nullptr)
{
*ceilinglightlevel = sec->GetCeilingLight();
}
FakeSide = WaterFakeSide::Center;
const sector_t *s = sec->GetHeightSec();
if (s != nullptr)
{
sector_t *heightsec = Thread->Viewport->viewpoint.sector->heightsec;
bool underwater = r_fakingunderwater ||
(heightsec && heightsec->floorplane.PointOnSide(Thread->Viewport->viewpoint.Pos) <= 0);
bool doorunderwater = false;
int diffTex = (s->MoreFlags & SECF_CLIPFAKEPLANES);
// Replace sector being drawn with a copy to be hacked
*tempsec = *sec;
// Replace floor and ceiling height with control sector's heights.
if (diffTex)
{
if (s->floorplane.CopyPlaneIfValid(&tempsec->floorplane, &sec->ceilingplane))
{
tempsec->SetTexture(sector_t::floor, s->GetTexture(sector_t::floor), false);
}
else if (s->MoreFlags & SECF_FAKEFLOORONLY)
{
if (underwater)
{
tempsec->Colormap = s->Colormap;
if (!(s->MoreFlags & SECF_NOFAKELIGHT))
{
tempsec->lightlevel = s->lightlevel;
if (floorlightlevel != nullptr)
{
*floorlightlevel = s->GetFloorLight();
}
if (ceilinglightlevel != nullptr)
{
*ceilinglightlevel = s->GetCeilingLight();
}
}
FakeSide = WaterFakeSide::BelowFloor;
return tempsec;
}
return sec;
}
}
else
{
tempsec->floorplane = s->floorplane;
}
if (!(s->MoreFlags & SECF_FAKEFLOORONLY))
{
if (diffTex)
{
if (s->ceilingplane.CopyPlaneIfValid(&tempsec->ceilingplane, &sec->floorplane))
{
tempsec->SetTexture(sector_t::ceiling, s->GetTexture(sector_t::ceiling), false);
}
}
else
{
tempsec->ceilingplane = s->ceilingplane;
}
}
double refceilz = s->ceilingplane.ZatPoint(Thread->Viewport->viewpoint.Pos);
double orgceilz = sec->ceilingplane.ZatPoint(Thread->Viewport->viewpoint.Pos);
#if 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 (backline && !r_fakingunderwater && backline->frontsector->heightsec == nullptr)
{
if (frontcz1 <= s->floorplane.ZatPoint(backline->v1) &&
frontcz2 <= s->floorplane.ZatPoint(backline->v2))
{
// Check that the window is actually visible
for (int z = backx1; z < backx2; ++z)
{
if (floorclip[z] > ceilingclip[z])
{
doorunderwater = true;
r_fakingunderwater = true;
break;
}
}
}
}
#endif
if (underwater || doorunderwater)
{
tempsec->floorplane = sec->floorplane;
tempsec->ceilingplane = s->floorplane;
tempsec->ceilingplane.FlipVert();
tempsec->ceilingplane.ChangeHeight(-1 / 65536.);
tempsec->Colormap = s->Colormap;
}
// killough 11/98: prevent sudden light changes from non-water sectors:
if ((underwater && !backline) || doorunderwater)
{ // head-below-floor hack
tempsec->SetTexture(sector_t::floor, diffTex ? sec->GetTexture(sector_t::floor) : s->GetTexture(sector_t::floor), false);
tempsec->planes[sector_t::floor].xform = s->planes[sector_t::floor].xform;
tempsec->ceilingplane = s->floorplane;
tempsec->ceilingplane.FlipVert();
tempsec->ceilingplane.ChangeHeight(-1 / 65536.);
if (s->GetTexture(sector_t::ceiling) == skyflatnum)
{
tempsec->floorplane = tempsec->ceilingplane;
tempsec->floorplane.FlipVert();
tempsec->floorplane.ChangeHeight(+1 / 65536.);
tempsec->SetTexture(sector_t::ceiling, tempsec->GetTexture(sector_t::floor), false);
tempsec->planes[sector_t::ceiling].xform = tempsec->planes[sector_t::floor].xform;
}
else
{
tempsec->SetTexture(sector_t::ceiling, diffTex ? s->GetTexture(sector_t::floor) : s->GetTexture(sector_t::ceiling), false);
tempsec->planes[sector_t::ceiling].xform = s->planes[sector_t::ceiling].xform;
}
if (!(s->MoreFlags & SECF_NOFAKELIGHT))
{
tempsec->lightlevel = s->lightlevel;
if (floorlightlevel != nullptr)
{
*floorlightlevel = s->GetFloorLight();
}
if (ceilinglightlevel != nullptr)
{
*ceilinglightlevel = s->GetCeilingLight();
}
}
FakeSide = WaterFakeSide::BelowFloor;
}
else if (heightsec && heightsec->ceilingplane.PointOnSide(Thread->Viewport->viewpoint.Pos) <= 0 &&
orgceilz > refceilz && !(s->MoreFlags & SECF_FAKEFLOORONLY))
{ // Above-ceiling hack
tempsec->ceilingplane = s->ceilingplane;
tempsec->floorplane = s->ceilingplane;
tempsec->floorplane.FlipVert();
tempsec->floorplane.ChangeHeight(+1 / 65536.);
tempsec->Colormap = s->Colormap;
tempsec->SetTexture(sector_t::ceiling, diffTex ? sec->GetTexture(sector_t::ceiling) : s->GetTexture(sector_t::ceiling), false);
tempsec->SetTexture(sector_t::floor, s->GetTexture(sector_t::ceiling), false);
tempsec->planes[sector_t::ceiling].xform = tempsec->planes[sector_t::floor].xform = s->planes[sector_t::ceiling].xform;
if (s->GetTexture(sector_t::floor) != skyflatnum)
{
tempsec->ceilingplane = sec->ceilingplane;
tempsec->SetTexture(sector_t::floor, s->GetTexture(sector_t::floor), false);
tempsec->planes[sector_t::floor].xform = s->planes[sector_t::floor].xform;
}
if (!(s->MoreFlags & SECF_NOFAKELIGHT))
{
tempsec->lightlevel = s->lightlevel;
if (floorlightlevel != nullptr)
{
*floorlightlevel = s->GetFloorLight();
}
if (ceilinglightlevel != nullptr)
{
*ceilinglightlevel = s->GetCeilingLight();
}
}
FakeSide = WaterFakeSide::AboveCeiling;
}
sec = tempsec; // Use other sector
}
return sec;
}
// Checks BSP node/subtree bounding box.
// Returns true if some part of the bbox might be visible.
bool RenderOpaquePass::CheckBBox(float *bspcoord)
{
static const int checkcoord[12][4] =
{
{ 3,0,2,1 },
{ 3,0,2,0 },
{ 3,1,2,0 },
{ 0 },
{ 2,0,2,1 },
{ 0,0,0,0 },
{ 3,1,3,0 },
{ 0 },
{ 2,0,3,1 },
{ 2,1,3,1 },
{ 2,1,3,0 }
};
int boxx;
int boxy;
int boxpos;
double x1, y1, x2, y2;
double rx1, ry1, rx2, ry2;
int sx1, sx2;
// Find the corners of the box
// that define the edges from current viewpoint.
if (Thread->Viewport->viewpoint.Pos.X <= bspcoord[BOXLEFT])
boxx = 0;
else if (Thread->Viewport->viewpoint.Pos.X < bspcoord[BOXRIGHT])
boxx = 1;
else
boxx = 2;
if (Thread->Viewport->viewpoint.Pos.Y >= bspcoord[BOXTOP])
boxy = 0;
else if (Thread->Viewport->viewpoint.Pos.Y > bspcoord[BOXBOTTOM])
boxy = 1;
else
boxy = 2;
boxpos = (boxy << 2) + boxx;
if (boxpos == 5)
return true;
x1 = bspcoord[checkcoord[boxpos][0]] - Thread->Viewport->viewpoint.Pos.X;
y1 = bspcoord[checkcoord[boxpos][1]] - Thread->Viewport->viewpoint.Pos.Y;
x2 = bspcoord[checkcoord[boxpos][2]] - Thread->Viewport->viewpoint.Pos.X;
y2 = bspcoord[checkcoord[boxpos][3]] - Thread->Viewport->viewpoint.Pos.Y;
// check clip list for an open space
// Sitting on a line?
if (y1 * (x1 - x2) + x1 * (y2 - y1) >= -EQUAL_EPSILON)
return true;
rx1 = x1 * Thread->Viewport->viewpoint.Sin - y1 * Thread->Viewport->viewpoint.Cos;
rx2 = x2 * Thread->Viewport->viewpoint.Sin - y2 * Thread->Viewport->viewpoint.Cos;
ry1 = x1 * Thread->Viewport->viewpoint.TanCos + y1 * Thread->Viewport->viewpoint.TanSin;
ry2 = x2 * Thread->Viewport->viewpoint.TanCos + y2 * Thread->Viewport->viewpoint.TanSin;
if (Thread->Portal->MirrorFlags & RF_XFLIP)
{
double t = -rx1;
rx1 = -rx2;
rx2 = t;
swapvalues(ry1, ry2);
}
auto viewport = Thread->Viewport.get();
if (rx1 >= -ry1)
{
if (rx1 > ry1) return false; // left edge is off the right side
if (ry1 == 0) return false;
sx1 = xs_RoundToInt(viewport->CenterX + rx1 * viewport->CenterX / ry1);
}
else
{
if (rx2 < -ry2) return false; // wall is off the left side
if (rx1 - rx2 - ry2 + ry1 == 0) return false; // wall does not intersect view volume
sx1 = 0;
}
if (rx2 <= ry2)
{
if (rx2 < -ry2) return false; // right edge is off the left side
if (ry2 == 0) return false;
sx2 = xs_RoundToInt(viewport->CenterX + rx2 * viewport->CenterX / ry2);
}
else
{
if (rx1 > ry1) return false; // wall is off the right side
if (ry2 - ry1 - rx2 + rx1 == 0) return false; // wall does not intersect view volume
sx2 = viewwidth;
}
// Find the first clippost that touches the source post
// (adjacent pixels are touching).
return Thread->ClipSegments->IsVisible(sx1, sx2);
}
void RenderOpaquePass::AddPolyobjs(subsector_t *sub)
{
if (sub->BSP == nullptr || sub->BSP->bDirty)
{
sub->BuildPolyBSP();
}
if (sub->BSP->Nodes.Size() == 0)
{
RenderSubsector(&sub->BSP->Subsectors[0]);
}
else
{
RenderBSPNode(&sub->BSP->Nodes.Last());
}
}
// kg3D - add fake segs, never rendered
void RenderOpaquePass::FakeDrawLoop(subsector_t *sub, VisiblePlane *floorplane, VisiblePlane *ceilingplane, bool foggy, FDynamicColormap *basecolormap)
{
int count;
seg_t* line;
count = sub->numlines;
line = sub->firstline;
while (count--)
{
if ((line->sidedef) && !(line->sidedef->Flags & WALLF_POLYOBJ))
{
renderline.Render(line, InSubsector, frontsector, nullptr, floorplane, ceilingplane, foggy, basecolormap);
}
line++;
}
}
void RenderOpaquePass::RenderSubsector(subsector_t *sub)
{
// Determine floor/ceiling planes.
// Add sprites of things in sector.
// Draw one or more line segments.
int count;
seg_t* line;
sector_t tempsec; // killough 3/7/98: deep water hack
int floorlightlevel; // killough 3/16/98: set floor lightlevel
int ceilinglightlevel; // killough 4/11/98
bool outersubsector;
int fll, cll, position;
FSectorPortal *portal;
// kg3D - fake floor stuff
VisiblePlane *backupfp;
VisiblePlane *backupcp;
//secplane_t templane;
lightlist_t *light;
if (InSubsector != nullptr)
{ // InSubsector is not nullptr. This means we are rendering from a mini-BSP.
outersubsector = false;
}
else
{
outersubsector = true;
InSubsector = sub;
}
#ifdef RANGECHECK
if (outersubsector && (unsigned)sub->Index() >= level.subsectors.Size())
I_Error("RenderSubsector: ss %i with numss = %u", sub->Index(), level.subsectors.Size());
#endif
assert(sub->sector != nullptr);
if (sub->polys)
{ // Render the polyobjs in the subsector first
AddPolyobjs(sub);
if (outersubsector)
{
InSubsector = nullptr;
}
return;
}
frontsector = sub->sector;
frontsector->MoreFlags |= SECF_DRAWN;
count = sub->numlines;
line = sub->firstline;
// killough 3/8/98, 4/4/98: Deep water / fake ceiling effect
frontsector = FakeFlat(frontsector, &tempsec, &floorlightlevel, &ceilinglightlevel, nullptr, 0, 0, 0, 0);
fll = floorlightlevel;
cll = ceilinglightlevel;
// [RH] set foggy flag
bool foggy = level.fadeto || frontsector->Colormap.FadeColor || (level.flags & LEVEL_HASFADETABLE);
// kg3D - fake lights
CameraLight *cameraLight = CameraLight::Instance();
FDynamicColormap *basecolormap;
if (cameraLight->FixedLightLevel() < 0 && frontsector->e && frontsector->e->XFloor.lightlist.Size())
{
light = P_GetPlaneLight(frontsector, &frontsector->ceilingplane, false);
basecolormap = GetColorTable(light->extra_colormap, frontsector->SpecialColors[sector_t::ceiling]);
// If this is the real ceiling, don't discard plane lighting R_FakeFlat()
// accounted for.
if (light->p_lightlevel != &frontsector->lightlevel)
{
ceilinglightlevel = *light->p_lightlevel;
}
}
else
{
basecolormap = (r_fullbrightignoresectorcolor && cameraLight->FixedLightLevel() >= 0) ? &FullNormalLight : GetColorTable(frontsector->Colormap, frontsector->SpecialColors[sector_t::ceiling]);
}
portal = frontsector->ValidatePortal(sector_t::ceiling);
VisiblePlane *ceilingplane = frontsector->ceilingplane.PointOnSide(Thread->Viewport->viewpoint.Pos) > 0 ||
frontsector->GetTexture(sector_t::ceiling) == skyflatnum ||
portal != nullptr ||
(frontsector->heightsec &&
!(frontsector->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) &&
frontsector->heightsec->GetTexture(sector_t::floor) == skyflatnum) ?
Thread->PlaneList->FindPlane(frontsector->ceilingplane, // killough 3/8/98
frontsector->GetTexture(sector_t::ceiling),
ceilinglightlevel + LightVisibility::ActualExtraLight(foggy, Thread->Viewport.get()), // killough 4/11/98
frontsector->GetAlpha(sector_t::ceiling),
!!(frontsector->GetFlags(sector_t::ceiling) & PLANEF_ADDITIVE),
frontsector->planes[sector_t::ceiling].xform,
frontsector->sky,
portal,
basecolormap
) : nullptr;
if (ceilingplane)
ceilingplane->AddLights(Thread, frontsector->lighthead);
if (cameraLight->FixedLightLevel() < 0 && frontsector->e && frontsector->e->XFloor.lightlist.Size())
{
light = P_GetPlaneLight(frontsector, &frontsector->floorplane, false);
basecolormap = GetColorTable(light->extra_colormap, frontsector->SpecialColors[sector_t::floor]);
// If this is the real floor, don't discard plane lighting R_FakeFlat()
// accounted for.
if (light->p_lightlevel != &frontsector->lightlevel)
{
floorlightlevel = *light->p_lightlevel;
}
}
else
{
basecolormap = (r_fullbrightignoresectorcolor && cameraLight->FixedLightLevel() >= 0) ? &FullNormalLight : GetColorTable(frontsector->Colormap, frontsector->SpecialColors[sector_t::floor]);
}
// killough 3/7/98: Add (x,y) offsets to flats, add deep water check
// killough 3/16/98: add floorlightlevel
// killough 10/98: add support for skies transferred from sidedefs
portal = frontsector->ValidatePortal(sector_t::floor);
VisiblePlane *floorplane = frontsector->floorplane.PointOnSide(Thread->Viewport->viewpoint.Pos) > 0 || // killough 3/7/98
frontsector->GetTexture(sector_t::floor) == skyflatnum ||
portal != nullptr ||
(frontsector->heightsec &&
!(frontsector->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) &&
frontsector->heightsec->GetTexture(sector_t::ceiling) == skyflatnum) ?
Thread->PlaneList->FindPlane(frontsector->floorplane,
frontsector->GetTexture(sector_t::floor),
floorlightlevel + LightVisibility::ActualExtraLight(foggy, Thread->Viewport.get()), // killough 3/16/98
frontsector->GetAlpha(sector_t::floor),
!!(frontsector->GetFlags(sector_t::floor) & PLANEF_ADDITIVE),
frontsector->planes[sector_t::floor].xform,
frontsector->sky,
portal,
basecolormap
) : nullptr;
if (floorplane)
floorplane->AddLights(Thread, frontsector->lighthead);
// kg3D - fake planes rendering
if (r_3dfloors && frontsector->e && frontsector->e->XFloor.ffloors.Size())
{
backupfp = floorplane;
backupcp = ceilingplane;
Clip3DFloors *clip3d = Thread->Clip3D.get();
// first check all floors
for (int i = 0; i < (int)frontsector->e->XFloor.ffloors.Size(); i++)
{
clip3d->SetFakeFloor(frontsector->e->XFloor.ffloors[i]);
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_EXISTS)) continue;
if (!clip3d->fakeFloor->fakeFloor->model) continue;
if (clip3d->fakeFloor->fakeFloor->bottom.plane->isSlope()) continue;
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_NOSHADE) || (clip3d->fakeFloor->fakeFloor->flags & (FF_RENDERPLANES | FF_RENDERSIDES)))
{
clip3d->AddHeight(clip3d->fakeFloor->fakeFloor->top.plane, frontsector);
}
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_RENDERPLANES)) continue;
if (clip3d->fakeFloor->fakeFloor->alpha == 0) continue;
if (clip3d->fakeFloor->fakeFloor->flags & FF_THISINSIDE && clip3d->fakeFloor->fakeFloor->flags & FF_INVERTSECTOR) continue;
clip3d->fakeAlpha = MIN<fixed_t>(Scale(clip3d->fakeFloor->fakeFloor->alpha, OPAQUE, 255), OPAQUE);
if (clip3d->fakeFloor->validcount != validcount)
{
clip3d->fakeFloor->validcount = validcount;
clip3d->NewClip();
}
double fakeHeight = clip3d->fakeFloor->fakeFloor->top.plane->ZatPoint(frontsector->centerspot);
if (fakeHeight < Thread->Viewport->viewpoint.Pos.Z &&
fakeHeight > frontsector->floorplane.ZatPoint(frontsector->centerspot))
{
clip3d->fake3D = FAKE3D_FAKEFLOOR;
tempsec = *clip3d->fakeFloor->fakeFloor->model;
tempsec.floorplane = *clip3d->fakeFloor->fakeFloor->top.plane;
tempsec.ceilingplane = *clip3d->fakeFloor->fakeFloor->bottom.plane;
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_THISINSIDE) && !(clip3d->fakeFloor->fakeFloor->flags & FF_INVERTSECTOR))
{
tempsec.SetTexture(sector_t::floor, tempsec.GetTexture(sector_t::ceiling));
position = sector_t::ceiling;
}
else position = sector_t::floor;
frontsector = &tempsec;
if (cameraLight->FixedLightLevel() < 0 && sub->sector->e->XFloor.lightlist.Size())
{
light = P_GetPlaneLight(sub->sector, &frontsector->floorplane, false);
basecolormap = GetColorTable(light->extra_colormap);
floorlightlevel = *light->p_lightlevel;
}
ceilingplane = nullptr;
floorplane = Thread->PlaneList->FindPlane(frontsector->floorplane,
frontsector->GetTexture(sector_t::floor),
floorlightlevel + LightVisibility::ActualExtraLight(foggy, Thread->Viewport.get()), // killough 3/16/98
frontsector->GetAlpha(sector_t::floor),
!!(clip3d->fakeFloor->fakeFloor->flags & FF_ADDITIVETRANS),
frontsector->planes[position].xform,
frontsector->sky,
nullptr,
basecolormap);
if (floorplane)
floorplane->AddLights(Thread, frontsector->lighthead);
FakeDrawLoop(sub, floorplane, ceilingplane, foggy, basecolormap);
clip3d->fake3D = 0;
frontsector = sub->sector;
}
}
// and now ceilings
for (unsigned int i = 0; i < frontsector->e->XFloor.ffloors.Size(); i++)
{
clip3d->SetFakeFloor(frontsector->e->XFloor.ffloors[i]);
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_EXISTS)) continue;
if (!clip3d->fakeFloor->fakeFloor->model) continue;
if (clip3d->fakeFloor->fakeFloor->top.plane->isSlope()) continue;
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_NOSHADE) || (clip3d->fakeFloor->fakeFloor->flags & (FF_RENDERPLANES | FF_RENDERSIDES)))
{
clip3d->AddHeight(clip3d->fakeFloor->fakeFloor->bottom.plane, frontsector);
}
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_RENDERPLANES)) continue;
if (clip3d->fakeFloor->fakeFloor->alpha == 0) continue;
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_THISINSIDE) && (clip3d->fakeFloor->fakeFloor->flags & (FF_SWIMMABLE | FF_INVERTSECTOR)) == (FF_SWIMMABLE | FF_INVERTSECTOR)) continue;
clip3d->fakeAlpha = MIN<fixed_t>(Scale(clip3d->fakeFloor->fakeFloor->alpha, OPAQUE, 255), OPAQUE);
if (clip3d->fakeFloor->validcount != validcount)
{
clip3d->fakeFloor->validcount = validcount;
clip3d->NewClip();
}
double fakeHeight = clip3d->fakeFloor->fakeFloor->bottom.plane->ZatPoint(frontsector->centerspot);
if (fakeHeight > Thread->Viewport->viewpoint.Pos.Z &&
fakeHeight < frontsector->ceilingplane.ZatPoint(frontsector->centerspot))
{
clip3d->fake3D = FAKE3D_FAKECEILING;
tempsec = *clip3d->fakeFloor->fakeFloor->model;
tempsec.floorplane = *clip3d->fakeFloor->fakeFloor->top.plane;
tempsec.ceilingplane = *clip3d->fakeFloor->fakeFloor->bottom.plane;
if ((!(clip3d->fakeFloor->fakeFloor->flags & FF_THISINSIDE) && !(clip3d->fakeFloor->fakeFloor->flags & FF_INVERTSECTOR)) ||
(clip3d->fakeFloor->fakeFloor->flags & FF_THISINSIDE && clip3d->fakeFloor->fakeFloor->flags & FF_INVERTSECTOR))
{
tempsec.SetTexture(sector_t::ceiling, tempsec.GetTexture(sector_t::floor));
position = sector_t::floor;
}
else position = sector_t::ceiling;
frontsector = &tempsec;
tempsec.ceilingplane.ChangeHeight(-1 / 65536.);
if (cameraLight->FixedLightLevel() < 0 && sub->sector->e->XFloor.lightlist.Size())
{
light = P_GetPlaneLight(sub->sector, &frontsector->ceilingplane, false);
basecolormap = GetColorTable(light->extra_colormap);
ceilinglightlevel = *light->p_lightlevel;
}
tempsec.ceilingplane.ChangeHeight(1 / 65536.);
floorplane = nullptr;
ceilingplane = Thread->PlaneList->FindPlane(frontsector->ceilingplane, // killough 3/8/98
frontsector->GetTexture(sector_t::ceiling),
ceilinglightlevel + LightVisibility::ActualExtraLight(foggy, Thread->Viewport.get()), // killough 4/11/98
frontsector->GetAlpha(sector_t::ceiling),
!!(clip3d->fakeFloor->fakeFloor->flags & FF_ADDITIVETRANS),
frontsector->planes[position].xform,
frontsector->sky,
nullptr,
basecolormap);
if (ceilingplane)
ceilingplane->AddLights(Thread, frontsector->lighthead);
FakeDrawLoop(sub, floorplane, ceilingplane, foggy, basecolormap);
clip3d->fake3D = 0;
frontsector = sub->sector;
}
}
clip3d->fakeFloor = nullptr;
floorplane = backupfp;
ceilingplane = backupcp;
}
basecolormap = GetColorTable(frontsector->Colormap, frontsector->SpecialColors[sector_t::sprites], true);
floorlightlevel = fll;
ceilinglightlevel = cll;
// killough 9/18/98: Fix underwater slowdown, by passing real sector
// instead of fake one. Improve sprite lighting by basing sprite
// lightlevels on floor & ceiling lightlevels in the surrounding area.
// [RH] Handle sprite lighting like Duke 3D: If the ceiling is a sky, sprites are lit by
// it, otherwise they are lit by the floor.
AddSprites(sub->sector, frontsector->GetTexture(sector_t::ceiling) == skyflatnum ? ceilinglightlevel : floorlightlevel, FakeSide, foggy, basecolormap);
// [RH] Add particles
if ((unsigned int)(sub->Index()) < level.subsectors.Size())
{ // Only do it for the main BSP.
int shade = LightVisibility::LightLevelToShade((floorlightlevel + ceilinglightlevel) / 2 + LightVisibility::ActualExtraLight(foggy, Thread->Viewport.get()), foggy);
for (int i = ParticlesInSubsec[sub->Index()]; i != NO_PARTICLE; i = Particles[i].snext)
{
RenderParticle::Project(Thread, Particles + i, sub->sector, shade, FakeSide, foggy);
}
}
count = sub->numlines;
line = sub->firstline;
basecolormap = GetColorTable(frontsector->Colormap, frontsector->SpecialColors[sector_t::walltop]);
while (count--)
{
if (!outersubsector || line->sidedef == nullptr || !(line->sidedef->Flags & WALLF_POLYOBJ))
{
// kg3D - fake planes bounding calculation
if (r_3dfloors && line->backsector && frontsector->e && line->backsector->e->XFloor.ffloors.Size())
{
backupfp = floorplane;
backupcp = ceilingplane;
floorplane = nullptr;
ceilingplane = nullptr;
Clip3DFloors *clip3d = Thread->Clip3D.get();
for (unsigned int i = 0; i < line->backsector->e->XFloor.ffloors.Size(); i++)
{
clip3d->SetFakeFloor(line->backsector->e->XFloor.ffloors[i]);
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_EXISTS)) continue;
if (!(clip3d->fakeFloor->fakeFloor->flags & FF_RENDERPLANES)) continue;
if (!clip3d->fakeFloor->fakeFloor->model) continue;
clip3d->fake3D = FAKE3D_FAKEBACK;
tempsec = *clip3d->fakeFloor->fakeFloor->model;
tempsec.floorplane = *clip3d->fakeFloor->fakeFloor->top.plane;
tempsec.ceilingplane = *clip3d->fakeFloor->fakeFloor->bottom.plane;
if (clip3d->fakeFloor->validcount != validcount)
{
clip3d->fakeFloor->validcount = validcount;
clip3d->NewClip();
}
renderline.Render(line, InSubsector, frontsector, &tempsec, floorplane, ceilingplane, foggy, basecolormap); // fake
}
clip3d->fakeFloor = nullptr;
clip3d->fake3D = 0;
floorplane = backupfp;
ceilingplane = backupcp;
}
renderline.Render(line, InSubsector, frontsector, nullptr, floorplane, ceilingplane, foggy, basecolormap); // now real
}
line++;
}
if (outersubsector)
{
InSubsector = nullptr;
}
}
void RenderOpaquePass::RenderScene()
{
SeenSpriteSectors.clear();
SeenActors.clear();
InSubsector = nullptr;
RenderBSPNode(level.HeadNode()); // The head node is the last node output.
}
//
// RenderBSPNode
// Renders all subsectors below a given node, traversing subtree recursively.
// Just call with BSP root and -1.
// killough 5/2/98: reformatted, removed tail recursion
void RenderOpaquePass::RenderBSPNode(void *node)
{
if (level.nodes.Size() == 0)
{
RenderSubsector(&level.subsectors[0]);
return;
}
while (!((size_t)node & 1)) // Keep going until found a subsector
{
node_t *bsp = (node_t *)node;
// Decide which side the view point is on.
int side = R_PointOnSide(Thread->Viewport->viewpoint.Pos, bsp);
// Recursively divide front space (toward the viewer).
RenderBSPNode(bsp->children[side]);
// Possibly divide back space (away from the viewer).
side ^= 1;
if (!CheckBBox(bsp->bbox[side]))
return;
node = bsp->children[side];
}
RenderSubsector((subsector_t *)((uint8_t *)node - 1));
}
void RenderOpaquePass::ClearClip()
{
// clip ceiling to console bottom
fillshort(floorclip, viewwidth, viewheight);
fillshort(ceilingclip, viewwidth, !screen->Accel2D && ConBottom > viewwindowy && !Thread->Viewport->RenderingToCanvas() ? (ConBottom - viewwindowy) : 0);
}
void RenderOpaquePass::AddSprites(sector_t *sec, int lightlevel, WaterFakeSide fakeside, bool foggy, FDynamicColormap *basecolormap)
{
// BSP is traversed by subsector.
// A sector might have been split into several
// subsectors during BSP building.
// Thus we check whether it was already added.
if (sec->touching_renderthings == nullptr || SeenSpriteSectors.find(sec) != SeenSpriteSectors.end()/*|| sec->validcount == validcount*/)
return;
// Well, now it will be done.
//sec->validcount = validcount;
SeenSpriteSectors.insert(sec);
int spriteshade = LightVisibility::LightLevelToShade(lightlevel + LightVisibility::ActualExtraLight(foggy, Thread->Viewport.get()), foggy);
// Handle all things in sector.
for (auto p = sec->touching_renderthings; p != nullptr; p = p->m_snext)
{
auto thing = p->m_thing;
if (SeenActors.find(thing) != SeenActors.end()) continue;
SeenActors.insert(thing);
//if (thing->validcount == validcount) continue;
//thing->validcount = validcount;
FIntCVar *cvar = thing->GetInfo()->distancecheck;
if (cvar != nullptr && *cvar >= 0)
{
double dist = (thing->Pos() - Thread->Viewport->viewpoint.Pos).LengthSquared();
double check = (double)**cvar;
if (dist >= check * check)
{
continue;
}
}
// find fake level
F3DFloor *fakeceiling = nullptr;
F3DFloor *fakefloor = nullptr;
for (auto rover : thing->Sector->e->XFloor.ffloors)
{
if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERPLANES)) continue;
if (!(rover->flags & FF_SOLID) || rover->alpha != 255) continue;
if (!fakefloor)
{
if (!rover->top.plane->isSlope())
{
if (rover->top.plane->ZatPoint(0., 0.) <= thing->Z()) fakefloor = rover;
}
}
if (!rover->bottom.plane->isSlope())
{
if (rover->bottom.plane->ZatPoint(0., 0.) >= thing->Top()) fakeceiling = rover;
}
}
if (IsPotentiallyVisible(thing))
{
ThingSprite sprite;
if (GetThingSprite(thing, sprite))
{
FDynamicColormap *thingColormap = basecolormap;
int thingShade = spriteshade;
if (sec->sectornum != thing->Sector->sectornum) // compare sectornums to account for R_FakeFlat copies.
{
int lightlevel = thing->Sector->GetTexture(sector_t::ceiling) == skyflatnum ? thing->Sector->GetCeilingLight() : thing->Sector->GetFloorLight();
thingShade = LightVisibility::LightLevelToShade(lightlevel + LightVisibility::ActualExtraLight(foggy, Thread->Viewport.get()), foggy);
thingColormap = GetColorTable(thing->Sector->Colormap, thing->Sector->SpecialColors[sector_t::sprites], true);
}
if ((sprite.renderflags & RF_SPRITETYPEMASK) == RF_WALLSPRITE)
{
RenderWallSprite::Project(Thread, thing, sprite.pos, sprite.picnum, sprite.spriteScale, sprite.renderflags, thingShade, foggy, thingColormap);
}
else if (sprite.voxel)
{
RenderVoxel::Project(Thread, thing, sprite.pos, sprite.voxel, sprite.spriteScale, sprite.renderflags, fakeside, fakefloor, fakeceiling, sec, thingShade, foggy, thingColormap);
}
else
{
RenderSprite::Project(Thread, thing, sprite.pos, sprite.tex, sprite.spriteScale, sprite.renderflags, fakeside, fakefloor, fakeceiling, sec, thingShade, foggy, thingColormap);
}
}
}
}
}
bool RenderOpaquePass::IsPotentiallyVisible(AActor *thing)
{
// Don't waste time projecting sprites that are definitely not visible.
if (thing == nullptr ||
(thing->renderflags & RF_INVISIBLE) ||
!thing->RenderStyle.IsVisible(thing->Alpha) ||
!thing->IsVisibleToPlayer() ||
!thing->IsInsideVisibleAngles())
{
return false;
}
// [ZZ] Or less definitely not visible (hue)
// [ZZ] 10.01.2016: don't try to clip stuff inside a skybox against the current portal.
RenderPortal *renderportal = Thread->Portal.get();
if (!renderportal->CurrentPortalInSkybox && renderportal->CurrentPortal && !!P_PointOnLineSidePrecise(thing->Pos(), renderportal->CurrentPortal->dst))
return false;
return true;
}
bool RenderOpaquePass::GetThingSprite(AActor *thing, ThingSprite &sprite)
{
sprite.pos = thing->InterpolatedPosition(Thread->Viewport->viewpoint.TicFrac);
sprite.pos.Z += thing->GetBobOffset(Thread->Viewport->viewpoint.TicFrac);
sprite.spritenum = thing->sprite;
sprite.tex = nullptr;
sprite.voxel = nullptr;
sprite.spriteScale = thing->Scale;
sprite.renderflags = thing->renderflags;
if (thing->player != nullptr)
{
P_CheckPlayerSprite(thing, sprite.spritenum, sprite.spriteScale);
}
if (thing->picnum.isValid())
{
sprite.picnum = thing->picnum;
sprite.tex = TexMan(sprite.picnum);
if (sprite.tex->UseType == FTexture::TEX_Null)
{
return false;
}
if (sprite.tex->Rotations != 0xFFFF)
{
// choose a different rotation based on player view
spriteframe_t *sprframe = &SpriteFrames[sprite.tex->Rotations];
DAngle ang = (sprite.pos - Thread->Viewport->viewpoint.Pos).Angle();
angle_t rot;
if (sprframe->Texture[0] == sprframe->Texture[1])
{
if (thing->flags7 & MF7_SPRITEANGLE)
rot = (thing->SpriteAngle + 45.0 / 2 * 9).BAMs() >> 28;
else
rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + 45.0 / 2 * 9).BAMs() >> 28;
}
else
{
if (thing->flags7 & MF7_SPRITEANGLE)
rot = (thing->SpriteAngle + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28;
else
rot = (ang - (thing->Angles.Yaw + thing->SpriteRotation) + (45.0 / 2 * 9 - 180.0 / 16)).BAMs() >> 28;
}
sprite.picnum = sprframe->Texture[rot];
if (sprframe->Flip & (1 << rot))
{
sprite.renderflags ^= RF_XFLIP;
}
sprite.tex = TexMan[sprite.picnum]; // Do not animate the rotation
}
}
else
{
// decide which texture to use for the sprite
if ((unsigned)sprite.spritenum >= sprites.Size())
{
DPrintf(DMSG_ERROR, "R_ProjectSprite: invalid sprite number %u\n", sprite.spritenum);
return false;
}
spritedef_t *sprdef = &sprites[sprite.spritenum];
if (thing->frame >= sprdef->numframes)
{
// If there are no frames at all for this sprite, don't draw it.
return false;
}
else
{
auto &viewpoint = Thread->Viewport->viewpoint;
DAngle sprangle = thing->GetSpriteAngle((sprite.pos - viewpoint.Pos).Angle(), viewpoint.TicFrac);
bool flipX;
FTextureID tex = sprdef->GetSpriteFrame(thing->frame, -1, sprangle, &flipX);
if (!tex.isValid()) return false;
if (flipX)
{
sprite.renderflags ^= RF_XFLIP;
}
sprite.tex = TexMan[tex]; // Do not animate the rotation
if (r_drawvoxels)
{
sprite.voxel = SpriteFrames[sprdef->spriteframes + thing->frame].Voxel;
}
}
if (sprite.voxel == nullptr && (sprite.tex == nullptr || sprite.tex->UseType == FTexture::TEX_Null))
{
return false;
}
if (sprite.spriteScale.Y < 0)
{
sprite.spriteScale.Y = -sprite.spriteScale.Y;
sprite.renderflags ^= RF_YFLIP;
}
if (sprite.spriteScale.X < 0)
{
sprite.spriteScale.X = -sprite.spriteScale.X;
sprite.renderflags ^= RF_XFLIP;
}
}
return true;
}
}