One line change in r_utility.cpp affecting a custom field. Orthographic projection sees "more" of the sceen as fov increases, but only up to 180 degrees, after which it will have no effect. Orthographic projection has no meaning for fov since there is no Frustum. But since this is fundamental to the engine, I am reinterpreting it. Cherno wanted this too. I'll update the wiki if this gets in.
1232 lines
37 KiB
C++
1232 lines
37 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// Copyright 1993-1996 id Software
|
|
// Copyright 1994-1996 Raven Software
|
|
// Copyright 1999-2016 Randy Heit
|
|
// Copyright 2002-2016 Christoph Oelckers
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// DESCRIPTION:
|
|
// Rendering main loop and setup functions,
|
|
// utility functions (BSP, geometry, trigonometry).
|
|
// See tables.c, too.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// HEADER FILES ------------------------------------------------------------
|
|
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
|
|
|
|
#include "doomdef.h"
|
|
#include "d_net.h"
|
|
#include "doomstat.h"
|
|
#include "m_random.h"
|
|
#include "m_bbox.h"
|
|
#include "r_sky.h"
|
|
#include "st_stuff.h"
|
|
#include "c_dispatch.h"
|
|
#include "v_video.h"
|
|
#include "stats.h"
|
|
#include "i_video.h"
|
|
#include "a_sharedglobal.h"
|
|
#include "p_3dmidtex.h"
|
|
#include "r_data/r_interpolate.h"
|
|
#include "po_man.h"
|
|
#include "p_effect.h"
|
|
#include "st_start.h"
|
|
#include "v_font.h"
|
|
#include "swrenderer/r_renderer.h"
|
|
#include "serializer.h"
|
|
#include "r_utility.h"
|
|
#include "d_player.h"
|
|
#include "p_local.h"
|
|
#include "g_levellocals.h"
|
|
#include "p_maputl.h"
|
|
#include "sbar.h"
|
|
#include "vm.h"
|
|
#include "i_time.h"
|
|
#include "actorinlines.h"
|
|
#include "g_game.h"
|
|
#include "i_system.h"
|
|
#include "v_draw.h"
|
|
#include "i_interface.h"
|
|
#include "d_main.h"
|
|
|
|
const float MY_SQRT2 = 1.41421356237309504880; // sqrt(2)
|
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
|
|
|
extern bool DrawFSHUD; // [RH] Defined in d_main.cpp
|
|
EXTERN_CVAR (Bool, cl_capfps)
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
struct InterpolationViewer
|
|
{
|
|
struct instance
|
|
{
|
|
DVector3 Pos, ViewPos;
|
|
DRotator Angles;
|
|
DRotator ViewAngles;
|
|
};
|
|
|
|
AActor* ViewActor;
|
|
DVector3 ViewOffset, RelativeViewOffset; // This has to be a separate field since it needs the real-time mouse angles.
|
|
DRotator AngleOffsets;
|
|
int prevTic;
|
|
instance Old, New;
|
|
};
|
|
|
|
// PRIVATE DATA DECLARATIONS -----------------------------------------------
|
|
static TArray<InterpolationViewer> PastViewers;
|
|
static FRandom pr_torchflicker ("TorchFlicker");
|
|
static FRandom pr_hom;
|
|
bool NoInterpolateView; // GL needs access to this.
|
|
static TArray<DVector3a> InterpolationPath;
|
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
|
|
CVAR (Bool, r_deathcamera, false, CVAR_ARCHIVE)
|
|
CVAR (Int, r_clearbuffer, 0, 0)
|
|
CVAR (Bool, r_drawvoxels, true, 0)
|
|
CVAR (Bool, r_drawplayersprites, true, 0) // [RH] Draw player sprites?
|
|
CVARD (Bool, r_radarclipper, false, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_CHEAT, "Use the horizontal clipper from camera->tracer's perspective")
|
|
CVARD (Bool, r_dithertransparency, false, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_CHEAT, "Use dithered-transparency shading for actor-occluding level geometry")
|
|
CUSTOM_CVAR(Float, r_quakeintensity, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
|
|
{
|
|
if (self < 0.f) self = 0.f;
|
|
else if (self > 1.f) self = 1.f;
|
|
}
|
|
|
|
CUSTOM_CVARD(Int, r_actorspriteshadow, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "render actor sprite shadows. 0 = off, 1 = default, 2 = always on")
|
|
{
|
|
if (self < 0)
|
|
self = 0;
|
|
else if (self > 2)
|
|
self = 2;
|
|
}
|
|
CUSTOM_CVARD(Float, r_actorspriteshadowdist, 1500.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "how far sprite shadows should be rendered")
|
|
{
|
|
if (self < 0.f)
|
|
self = 0.f;
|
|
else if (self > 8192.f)
|
|
self = 8192.f;
|
|
}
|
|
CUSTOM_CVARD(Float, r_actorspriteshadowalpha, 0.5, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "maximum sprite shadow opacity, only effective with hardware renderers (0.0 = fully transparent, 1.0 = opaque)")
|
|
{
|
|
if (self < 0.f)
|
|
self = 0.f;
|
|
else if (self > 1.f)
|
|
self = 1.f;
|
|
}
|
|
CUSTOM_CVARD(Float, r_actorspriteshadowfadeheight, 0.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "distance over which sprite shadows should fade, only effective with hardware renderers (0 = infinite)")
|
|
{
|
|
if (self < 0.f)
|
|
self = 0.f;
|
|
else if (self > 8192.f)
|
|
self = 8192.f;
|
|
}
|
|
|
|
int viewwindowx;
|
|
int viewwindowy;
|
|
int viewwidth;
|
|
int viewheight;
|
|
|
|
FRenderViewpoint::FRenderViewpoint()
|
|
{
|
|
player = nullptr;
|
|
Pos = { 0.0, 0.0, 0.0 };
|
|
ActorPos = { 0.0, 0.0, 0.0 };
|
|
Angles = { nullAngle, nullAngle, nullAngle };
|
|
Path[0] = { 0.0, 0.0, 0.0 };
|
|
Path[1] = { 0.0, 0.0, 0.0 };
|
|
Cos = 0.0;
|
|
Sin = 0.0;
|
|
TanCos = 0.0;
|
|
TanSin = 0.0;
|
|
PitchCos = 0.0;
|
|
PitchSin = 0.0;
|
|
floordistfact = 0.0;
|
|
cotfloor = 0.0;
|
|
camera = nullptr;
|
|
sector = nullptr;
|
|
FieldOfView = DAngle::fromDeg(90.); // Angles in the SCREENWIDTH wide window
|
|
ScreenProj = 0.0;
|
|
TicFrac = 0.0;
|
|
FrameTime = 0;
|
|
extralight = 0;
|
|
showviewer = false;
|
|
}
|
|
|
|
FRenderViewpoint r_viewpoint;
|
|
FViewWindow r_viewwindow;
|
|
|
|
bool r_NoInterpolate;
|
|
|
|
angle_t LocalViewAngle;
|
|
int LocalViewPitch;
|
|
bool LocalKeyboardTurner;
|
|
|
|
int setblocks;
|
|
|
|
unsigned int R_OldBlend = ~0;
|
|
int validcount = 1; // increment every time a check is made
|
|
int dl_validcount = 1; // increment every time a check is made
|
|
int freelookviewheight;
|
|
|
|
DVector3a view;
|
|
DAngle viewpitch;
|
|
|
|
DEFINE_GLOBAL(LocalViewPitch);
|
|
|
|
// CODE --------------------------------------------------------------------
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_SetFOV
|
|
//
|
|
// Changes the field of view in degrees
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_SetFOV (FRenderViewpoint &viewpoint, DAngle fov)
|
|
{
|
|
|
|
if (fov < DAngle::fromDeg(5.)) fov = DAngle::fromDeg(5.);
|
|
else if (fov > DAngle::fromDeg(170.)) fov = DAngle::fromDeg(170.);
|
|
if (fov != viewpoint.FieldOfView)
|
|
{
|
|
viewpoint.FieldOfView = fov;
|
|
setsizeneeded = true;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_SetViewSize
|
|
//
|
|
// Do not really change anything here, because it might be in the middle
|
|
// of a refresh. The change will take effect next refresh.
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_SetViewSize (int blocks)
|
|
{
|
|
setsizeneeded = true;
|
|
setblocks = blocks;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_SetWindow
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_SetWindow (FRenderViewpoint &viewpoint, FViewWindow &viewwindow, int windowSize, int fullWidth, int fullHeight, int stHeight, bool renderingToCanvas)
|
|
{
|
|
if (windowSize >= 11)
|
|
{
|
|
viewwidth = fullWidth;
|
|
freelookviewheight = viewheight = fullHeight;
|
|
}
|
|
else if (windowSize == 10)
|
|
{
|
|
viewwidth = fullWidth;
|
|
viewheight = stHeight;
|
|
freelookviewheight = fullHeight;
|
|
}
|
|
else
|
|
{
|
|
viewwidth = ((setblocks*fullWidth)/10) & (~15);
|
|
viewheight = ((setblocks*stHeight)/10)&~7;
|
|
freelookviewheight = ((setblocks*fullHeight)/10)&~7;
|
|
}
|
|
|
|
if (renderingToCanvas)
|
|
{
|
|
viewwindow.WidescreenRatio = fullWidth / (float)fullHeight;
|
|
}
|
|
else
|
|
{
|
|
viewwindow.WidescreenRatio = ActiveRatio(fullWidth, fullHeight);
|
|
DrawFSHUD = (windowSize == 11);
|
|
}
|
|
|
|
|
|
// [RH] Sky height fix for screens not 200 (or 240) pixels tall
|
|
R_InitSkyMap ();
|
|
|
|
viewwindow.centery = viewheight/2;
|
|
viewwindow.centerx = viewwidth/2;
|
|
if (AspectTallerThanWide(viewwindow.WidescreenRatio))
|
|
{
|
|
viewwindow.centerxwide = viewwindow.centerx;
|
|
}
|
|
else
|
|
{
|
|
viewwindow.centerxwide = viewwindow.centerx * AspectMultiplier(viewwindow.WidescreenRatio) / 48;
|
|
}
|
|
|
|
|
|
DAngle fov = viewpoint.FieldOfView;
|
|
|
|
// For widescreen displays, increase the FOV so that the middle part of the
|
|
// screen that would be visible on a 4:3 display has the requested FOV.
|
|
if (viewwindow.centerxwide != viewwindow.centerx)
|
|
{ // centerxwide is what centerx would be if the display was not widescreen
|
|
fov = DAngle::fromRad(2 * atan(viewwindow.centerx * tan(fov.Radians()/2) / double(viewwindow.centerxwide)));
|
|
if (fov > DAngle::fromDeg(170.)) fov = DAngle::fromDeg(170.);
|
|
}
|
|
viewwindow.FocalTangent = tan(fov.Radians() / 2);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_ExecuteSetViewSize
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_ExecuteSetViewSize (FRenderViewpoint &viewpoint, FViewWindow &viewwindow)
|
|
{
|
|
setsizeneeded = false;
|
|
|
|
R_SetWindow (viewpoint, viewwindow, setblocks, SCREENWIDTH, SCREENHEIGHT, StatusBar->GetTopOfStatusbar());
|
|
|
|
// Handle resize, e.g. smaller view windows with border and/or status bar.
|
|
viewwindowx = (screen->GetWidth() - viewwidth) >> 1;
|
|
|
|
// Same with base row offset.
|
|
viewwindowy = (viewwidth == screen->GetWidth()) ? 0 : (StatusBar->GetTopOfStatusbar() - viewheight) >> 1;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// r_visibility
|
|
//
|
|
// Controls how quickly light ramps across a 1/z range.
|
|
//
|
|
//==========================================================================
|
|
|
|
double R_ClampVisibility(double vis)
|
|
{
|
|
// Allow negative visibilities, just for novelty's sake
|
|
return clamp(vis, -204.7, 204.7); // (205 and larger do not work in 5:4 aspect ratio)
|
|
}
|
|
|
|
CUSTOM_CVAR(Float, r_visibility, 8.0f, CVAR_NOINITCALL)
|
|
{
|
|
if (netgame && self != 8.0f)
|
|
{
|
|
Printf("Visibility cannot be changed in net games.\n");
|
|
self = 8.0f;
|
|
}
|
|
else
|
|
{
|
|
float clampValue = (float)R_ClampVisibility(self);
|
|
if (self != clampValue)
|
|
self = clampValue;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_GetGlobVis
|
|
//
|
|
// Calculates the global visibility constant used by the software renderer
|
|
//
|
|
//==========================================================================
|
|
|
|
double R_GetGlobVis(const FViewWindow &viewwindow, double vis)
|
|
{
|
|
vis = R_ClampVisibility(vis);
|
|
|
|
double virtwidth = screen->GetWidth();
|
|
double virtheight = screen->GetHeight();
|
|
|
|
if (AspectTallerThanWide(viewwindow.WidescreenRatio))
|
|
{
|
|
virtheight = (virtheight * AspectMultiplier(viewwindow.WidescreenRatio)) / 48;
|
|
}
|
|
else
|
|
{
|
|
virtwidth = (virtwidth * AspectMultiplier(viewwindow.WidescreenRatio)) / 48;
|
|
}
|
|
|
|
double YaspectMul = 320.0 * virtheight / (200.0 * virtwidth);
|
|
double InvZtoScale = YaspectMul * viewwindow.centerx;
|
|
|
|
double wallVisibility = vis;
|
|
|
|
// Prevent overflow on walls
|
|
double maxVisForWall = (InvZtoScale * (screen->GetWidth() * r_Yaspect) / (viewwidth * screen->GetHeight() * viewwindow.FocalTangent));
|
|
maxVisForWall = 32767.0 / maxVisForWall;
|
|
if (vis < 0 && vis < -maxVisForWall)
|
|
wallVisibility = -maxVisForWall;
|
|
else if (vis > 0 && vis > maxVisForWall)
|
|
wallVisibility = maxVisForWall;
|
|
|
|
wallVisibility = InvZtoScale * screen->GetWidth() * AspectBaseHeight(viewwindow.WidescreenRatio) / (viewwidth * screen->GetHeight() * 3) * (wallVisibility * viewwindow.FocalTangent);
|
|
|
|
return wallVisibility / viewwindow.FocalTangent;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// CVAR screenblocks
|
|
//
|
|
// Selects the size of the visible window
|
|
//
|
|
//==========================================================================
|
|
|
|
CUSTOM_CVAR (Int, screenblocks, 10, CVAR_ARCHIVE)
|
|
{
|
|
if (self > 12)
|
|
self = 12;
|
|
else if (self < 3)
|
|
self = 3;
|
|
else
|
|
R_SetViewSize (self);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
//
|
|
//
|
|
//==========================================================================
|
|
|
|
FRenderer *CreateSWRenderer();
|
|
FRenderer* SWRenderer;
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_Init
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_Init ()
|
|
{
|
|
R_InitTranslationTables ();
|
|
R_SetViewSize (screenblocks);
|
|
|
|
if (SWRenderer == NULL)
|
|
{
|
|
SWRenderer = CreateSWRenderer();
|
|
}
|
|
|
|
SWRenderer->Init();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_Shutdown
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_Shutdown ()
|
|
{
|
|
if (SWRenderer != nullptr) delete SWRenderer;
|
|
SWRenderer = nullptr;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_NoInterpolation
|
|
//
|
|
//==========================================================================
|
|
|
|
//CVAR (Int, tf, 0, 0)
|
|
|
|
bool P_NoInterpolation(player_t const *player, AActor const *actor)
|
|
{
|
|
return player != nullptr
|
|
&& !(player->cheats & CF_INTERPVIEW)
|
|
&& player - players == consoleplayer
|
|
&& actor == player->mo
|
|
&& !demoplayback
|
|
&& !(player->cheats & (CF_TOTALLYFROZEN | CF_FROZEN))
|
|
&& player->playerstate == PST_LIVE
|
|
&& player->mo->reactiontime == 0
|
|
&& !NoInterpolateView
|
|
&& !paused
|
|
&& !LocalKeyboardTurner;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_InterpolateView
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_InterpolateView(FRenderViewpoint& viewPoint, const player_t* const player, const double ticFrac, InterpolationViewer* const iView)
|
|
{
|
|
if (NoInterpolateView)
|
|
{
|
|
InterpolationPath.Clear();
|
|
NoInterpolateView = false;
|
|
iView->Old = iView->New;
|
|
}
|
|
|
|
const double inverseTicFrac = 1.0 - ticFrac;
|
|
const auto viewLvl = viewPoint.ViewLevel;
|
|
|
|
DAngle prevYaw = iView->Old.Angles.Yaw;
|
|
DAngle curYaw = iView->New.Angles.Yaw;
|
|
if (!cl_capfps)
|
|
{
|
|
if ((iView->Old.Pos.X != iView->New.Pos.X || iView->Old.Pos.Y != iView->New.Pos.Y) && InterpolationPath.Size() > 0)
|
|
{
|
|
// Interpolating through line portals is a messy affair.
|
|
// What needs be done is to store the portal transitions of the camera actor as waypoints
|
|
// and then find out on which part of the path the current view lies.
|
|
double totalPathLength = 0.0;
|
|
double totalZDiff = 0.0;
|
|
DAngle totalYawDiff = nullAngle;
|
|
DVector3a oldPos = { { iView->Old.Pos.X, iView->Old.Pos.Y, 0.0 }, nullAngle };
|
|
DVector3a newPos = { { iView->New.Pos.X, iView->New.Pos.Y, 0.0 }, nullAngle };
|
|
InterpolationPath.Push(newPos); // Add this to the array to simplify the loops below.
|
|
|
|
for (size_t i = 0u; i < InterpolationPath.Size(); i += 2u)
|
|
{
|
|
const DVector3a& start = !i ? oldPos : InterpolationPath[i - 1u];
|
|
const DVector3a& end = InterpolationPath[i];
|
|
totalPathLength += (end.pos - start.pos).Length();
|
|
totalZDiff += start.pos.Z;
|
|
totalYawDiff += start.angle;
|
|
}
|
|
|
|
double interpolatedLength = totalPathLength * ticFrac;
|
|
double zDiff = 0.0;
|
|
DAngle yawDiff = nullAngle;
|
|
double prevViewZ = iView->Old.Pos.Z;
|
|
double curViewZ = iView->New.Pos.Z;
|
|
for (size_t i = 0u; i < InterpolationPath.Size(); i += 2u)
|
|
{
|
|
const DVector3a& start = !i ? oldPos : InterpolationPath[i - 1u];
|
|
const DVector3a& end = InterpolationPath[i];
|
|
const double fragmentLength = (end.pos - start.pos).Length();
|
|
zDiff += start.pos.Z;
|
|
yawDiff += start.angle;
|
|
|
|
if (fragmentLength <= interpolatedLength)
|
|
{
|
|
interpolatedLength -= fragmentLength;
|
|
}
|
|
else
|
|
{
|
|
prevViewZ += zDiff;
|
|
curViewZ -= totalZDiff - zDiff;
|
|
prevYaw += yawDiff;
|
|
curYaw -= totalYawDiff - yawDiff;
|
|
|
|
const DVector2 viewPos = start.pos.XY() + ((interpolatedLength / fragmentLength) * (end.pos - start.pos).XY());
|
|
viewPoint.Pos = { viewPos, prevViewZ * inverseTicFrac + curViewZ * ticFrac };
|
|
break;
|
|
}
|
|
}
|
|
|
|
InterpolationPath.Pop();
|
|
viewPoint.Path[0] = iView->Old.Pos;
|
|
viewPoint.Path[1] = viewPoint.Path[0] + (InterpolationPath[0].pos - viewPoint.Path[0]).XY().MakeResize(totalPathLength);
|
|
}
|
|
else
|
|
{
|
|
const int prevPortalGroup = viewLvl->PointInRenderSubsector(iView->Old.Pos)->sector->PortalGroup;
|
|
const int curPortalGroup = viewLvl->PointInRenderSubsector(iView->New.Pos)->sector->PortalGroup;
|
|
|
|
const DVector2 portalOffset = viewLvl->Displacements.getOffset(prevPortalGroup, curPortalGroup);
|
|
viewPoint.Pos = iView->Old.Pos * inverseTicFrac + (iView->New.Pos - portalOffset) * ticFrac;
|
|
viewPoint.Path[0] = viewPoint.Path[1] = iView->New.Pos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
viewPoint.Pos = iView->New.Pos;
|
|
viewPoint.Path[0] = viewPoint.Path[1] = iView->New.Pos;
|
|
}
|
|
|
|
// Due to interpolation this is not necessarily the same as the sector the camera is in.
|
|
viewPoint.sector = viewLvl->PointInRenderSubsector(viewPoint.Pos)->sector;
|
|
if (!viewPoint.IsAllowedOoB() || !V_IsHardwareRenderer())
|
|
{
|
|
bool moved = false;
|
|
while (!viewPoint.sector->PortalBlocksMovement(sector_t::ceiling))
|
|
{
|
|
if (viewPoint.Pos.Z > viewPoint.sector->GetPortalPlaneZ(sector_t::ceiling))
|
|
{
|
|
const DVector2 offset = viewPoint.sector->GetPortalDisplacement(sector_t::ceiling);
|
|
viewPoint.Pos += offset;
|
|
viewPoint.ActorPos += offset;
|
|
viewPoint.sector = viewPoint.sector->GetPortal(sector_t::ceiling)->mDestination;
|
|
moved = true;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!moved)
|
|
{
|
|
while (!viewPoint.sector->PortalBlocksMovement(sector_t::floor))
|
|
{
|
|
if (viewPoint.Pos.Z < viewPoint.sector->GetPortalPlaneZ(sector_t::floor))
|
|
{
|
|
const DVector2 offset = viewPoint.sector->GetPortalDisplacement(sector_t::floor);
|
|
viewPoint.Pos += offset;
|
|
viewPoint.ActorPos += offset;
|
|
viewPoint.sector = viewPoint.sector->GetPortal(sector_t::floor)->mDestination;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (P_NoInterpolation(player, viewPoint.camera))
|
|
{
|
|
viewPoint.Angles.Yaw = curYaw + DAngle::fromBam(LocalViewAngle);
|
|
const DAngle delta = player->centering ? nullAngle : DAngle::fromBam(LocalViewPitch);
|
|
viewPoint.Angles.Pitch = clamp<DAngle>((iView->New.Angles.Pitch - delta).Normalized180(), player->MinPitch, player->MaxPitch);
|
|
viewPoint.Angles.Roll = iView->New.Angles.Roll;
|
|
}
|
|
else
|
|
{
|
|
viewPoint.Angles.Pitch = iView->Old.Angles.Pitch + deltaangle(iView->Old.Angles.Pitch, iView->New.Angles.Pitch) * ticFrac;
|
|
viewPoint.Angles.Yaw = prevYaw + deltaangle(prevYaw, curYaw) * ticFrac;
|
|
viewPoint.Angles.Roll = iView->Old.Angles.Roll + deltaangle(iView->Old.Angles.Roll, iView->New.Angles.Roll) * ticFrac;
|
|
}
|
|
|
|
// Now that the base position and angles are set, add offsets.
|
|
|
|
const DViewPosition* const vPos = iView->ViewActor->ViewPos;
|
|
if (vPos != nullptr && !(vPos->Flags & VPSF_ABSOLUTEPOS)
|
|
&& (player == nullptr || gamestate == GS_TITLELEVEL || (!(player->cheats & CF_CHASECAM) && (!r_deathcamera || !(iView->ViewActor->flags6 & MF6_KILLED)))))
|
|
{
|
|
DVector3 vOfs = {};
|
|
if (player == nullptr || !(player->cheats & CF_NOVIEWPOSINTERP))
|
|
vOfs = iView->Old.ViewPos * inverseTicFrac + iView->New.ViewPos * ticFrac;
|
|
else
|
|
vOfs = iView->New.ViewPos;
|
|
|
|
if (vPos->Flags & VPSF_ABSOLUTEOFFSET)
|
|
iView->ViewOffset += vOfs;
|
|
else
|
|
iView->RelativeViewOffset += vOfs;
|
|
}
|
|
|
|
DVector3 posOfs = iView->ViewOffset;
|
|
if (!iView->RelativeViewOffset.isZero())
|
|
posOfs += DQuaternion::FromAngles(viewPoint.Angles.Yaw, viewPoint.Angles.Pitch, viewPoint.Angles.Roll) * iView->RelativeViewOffset;
|
|
|
|
// Now that we have the current interpolated position, offset from that directly (for view offset + chase cam).
|
|
if (!posOfs.isZero())
|
|
{
|
|
const double distance = posOfs.Length();
|
|
posOfs /= distance;
|
|
R_OffsetView(viewPoint, posOfs, distance);
|
|
}
|
|
|
|
viewPoint.Angles += iView->AngleOffsets;
|
|
|
|
// [MR] Apply the view angles as an offset if ABSVIEWANGLES isn't specified.
|
|
if (!(viewPoint.camera->flags8 & MF8_ABSVIEWANGLES))
|
|
{
|
|
if (player == nullptr || (player->cheats & CF_INTERPVIEWANGLES))
|
|
{
|
|
viewPoint.Angles.Yaw += iView->Old.ViewAngles.Yaw + deltaangle(iView->Old.ViewAngles.Yaw, iView->New.ViewAngles.Yaw) * ticFrac;
|
|
viewPoint.Angles.Pitch += iView->Old.ViewAngles.Pitch + deltaangle(iView->Old.ViewAngles.Pitch, iView->New.ViewAngles.Pitch) * ticFrac;
|
|
viewPoint.Angles.Roll += iView->Old.ViewAngles.Roll + deltaangle(iView->Old.ViewAngles.Roll, iView->New.ViewAngles.Roll) * ticFrac;
|
|
}
|
|
else
|
|
{
|
|
viewPoint.Angles += iView->New.ViewAngles;
|
|
}
|
|
}
|
|
|
|
viewPoint.Angles.Yaw = viewPoint.Angles.Yaw.Normalized180();
|
|
viewPoint.Angles.Pitch = viewPoint.Angles.Pitch.Normalized180();
|
|
viewPoint.Angles.Roll = viewPoint.Angles.Roll.Normalized180();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_ResetViewInterpolation
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_ResetViewInterpolation ()
|
|
{
|
|
InterpolationPath.Clear();
|
|
NoInterpolateView = true;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_SetViewAngle
|
|
// sets all values derived from the view yaw.
|
|
//
|
|
//==========================================================================
|
|
|
|
void FRenderViewpoint::SetViewAngle(const FViewWindow& viewWindow)
|
|
{
|
|
Sin = Angles.Yaw.Sin();
|
|
Cos = Angles.Yaw.Cos();
|
|
|
|
TanSin = viewWindow.FocalTangent * Sin;
|
|
TanCos = viewWindow.FocalTangent * Cos;
|
|
|
|
PitchSin = Angles.Pitch.Sin();
|
|
PitchCos = Angles.Pitch.Cos();
|
|
|
|
floordistfact = MY_SQRT2 + ( fabs(Cos) > fabs(Sin) ? 1.0/fabs(Cos) : 1.0/fabs(Sin) );
|
|
cotfloor = ( fabs(Cos) > fabs(Sin) ? fabs(Sin/Cos) : fabs(Cos/Sin) );
|
|
|
|
const DVector2 v = Angles.Yaw.ToVector();
|
|
ViewVector.X = v.X;
|
|
ViewVector.Y = v.Y;
|
|
HWAngles.Yaw = FAngle::fromDeg(270.0 - Angles.Yaw.Degrees());
|
|
|
|
if (IsOrtho() && (camera->ViewPos->Offset.XY().Length() > 0.0))
|
|
ScreenProj = 1.34396 / camera->ViewPos->Offset.Length() / tan (FieldOfView.Radians()*0.5); // [DVR] Estimated. +/-1 should be top/bottom of screen.
|
|
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_IsAllowedOoB()
|
|
// Checks if camera actor exists, has viewpos,
|
|
// and viewpos has VPSF_ALLOWOUTOFBOUNDS flag set.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool FRenderViewpoint::IsAllowedOoB()
|
|
{
|
|
return (camera && camera->ViewPos && (camera->ViewPos->Flags & VPSF_ALLOWOUTOFBOUNDS));
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_IsOrtho()
|
|
// Checks if camera actor exists, has viewpos,
|
|
// and viewpos has VPSF_ORTHOGRAPHIC flag set.
|
|
//
|
|
//==========================================================================
|
|
|
|
bool FRenderViewpoint::IsOrtho()
|
|
{
|
|
return (camera && camera->ViewPos && (camera->ViewPos->Flags & VPSF_ORTHOGRAPHIC));
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FindPastViewer
|
|
//
|
|
//==========================================================================
|
|
|
|
static InterpolationViewer *FindPastViewer (AActor *actor)
|
|
{
|
|
for (unsigned int i = 0; i < PastViewers.Size(); ++i)
|
|
{
|
|
if (PastViewers[i].ViewActor == actor)
|
|
{
|
|
return &PastViewers[i];
|
|
}
|
|
}
|
|
|
|
// Not found, so make a new one
|
|
InterpolationViewer iview;
|
|
memset(&iview, 0, sizeof(iview));
|
|
iview.ViewActor = actor;
|
|
iview.prevTic = -1;
|
|
InterpolationPath.Clear();
|
|
return &PastViewers[PastViewers.Push (iview)];
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_FreePastViewers
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_FreePastViewers ()
|
|
{
|
|
InterpolationPath.Clear();
|
|
PastViewers.Clear ();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_ClearPastViewer
|
|
//
|
|
// If the actor changed in a non-interpolatable way, remove it.
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_ClearPastViewer (AActor *actor)
|
|
{
|
|
InterpolationPath.Clear();
|
|
for (unsigned int i = 0; i < PastViewers.Size(); ++i)
|
|
{
|
|
if (PastViewers[i].ViewActor == actor)
|
|
{
|
|
// Found it, so remove it.
|
|
if (i == PastViewers.Size())
|
|
{
|
|
PastViewers.Delete (i);
|
|
}
|
|
else
|
|
{
|
|
PastViewers.Pop (PastViewers[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_RebuildViewInterpolation
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_RebuildViewInterpolation(player_t *player)
|
|
{
|
|
if (player == NULL || player->camera == NULL)
|
|
return;
|
|
|
|
if (!NoInterpolateView)
|
|
return;
|
|
NoInterpolateView = false;
|
|
|
|
InterpolationViewer *iview = FindPastViewer(player->camera);
|
|
|
|
iview->Old = iview->New;
|
|
InterpolationPath.Clear();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_GetViewInterpolationStatus
|
|
//
|
|
//==========================================================================
|
|
|
|
bool R_GetViewInterpolationStatus()
|
|
{
|
|
return NoInterpolateView;
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_ClearInterpolationPath
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_ClearInterpolationPath()
|
|
{
|
|
InterpolationPath.Clear();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_AddInterpolationPoint
|
|
//
|
|
//==========================================================================
|
|
|
|
void R_AddInterpolationPoint(const DVector3a &vec)
|
|
{
|
|
InterpolationPath.Push(vec);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// QuakePower
|
|
//
|
|
//==========================================================================
|
|
|
|
static double QuakePower(double factor, double intensity, double offset)
|
|
{
|
|
double randumb;
|
|
if (intensity == 0)
|
|
{
|
|
randumb = 0;
|
|
}
|
|
else
|
|
{
|
|
randumb = pr_torchflicker.GenRand_Real2() * (intensity * 2) - intensity;
|
|
}
|
|
return factor * (offset + randumb);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_DoActorTickerAngleChanges
|
|
//
|
|
//==========================================================================
|
|
|
|
static void R_DoActorTickerAngleChanges(player_t* const player, DRotator& angles, const double scale)
|
|
{
|
|
for (unsigned i = 0; i < 3; i++)
|
|
{
|
|
if (fabs(player->angleOffsetTargets[i].Degrees()) >= EQUAL_EPSILON)
|
|
angles[i] += player->angleOffsetTargets[i] * scale;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_SetupFrame
|
|
//
|
|
//==========================================================================
|
|
|
|
EXTERN_CVAR(Float, chase_dist)
|
|
EXTERN_CVAR(Float, chase_height)
|
|
|
|
void R_SetupFrame(FRenderViewpoint& viewPoint, const FViewWindow& viewWindow, AActor* const actor)
|
|
{
|
|
viewPoint.TicFrac = I_GetTimeFrac();
|
|
if (cl_capfps || r_NoInterpolate)
|
|
viewPoint.TicFrac = 1.0;
|
|
|
|
const int curTic = I_GetTime();
|
|
|
|
if (actor == nullptr)
|
|
I_Error("Tried to render from a null actor.");
|
|
|
|
viewPoint.ViewLevel = actor->Level;
|
|
|
|
player_t* player = actor->player;
|
|
if (player != nullptr && player->mo == actor)
|
|
{
|
|
if (player->camera == nullptr)
|
|
player->camera = player->mo;
|
|
|
|
viewPoint.camera = player->camera; // [RH] Use camera instead of view player.
|
|
}
|
|
else
|
|
{
|
|
viewPoint.camera = actor;
|
|
}
|
|
|
|
if (viewPoint.camera == nullptr)
|
|
I_Error("You lost your body. Bad dehacked work is likely to blame.");
|
|
|
|
InterpolationViewer* const iView = FindPastViewer(viewPoint.camera);
|
|
// Always reset these back to zero.
|
|
iView->ViewOffset.Zero();
|
|
iView->RelativeViewOffset.Zero();
|
|
iView->AngleOffsets.Zero();
|
|
if (iView->prevTic != -1 && curTic > iView->prevTic)
|
|
{
|
|
iView->prevTic = curTic;
|
|
iView->Old = iView->New;
|
|
}
|
|
|
|
const auto& mainView = r_viewpoint;
|
|
AActor* const client = players[consoleplayer].mo;
|
|
const bool matchPlayer = gamestate != GS_TITLELEVEL && viewPoint.camera->player == nullptr && (viewPoint.camera->renderflags2 & RF2_CAMFOLLOWSPLAYER);
|
|
const bool usePawn = matchPlayer ? mainView.camera != client : false;
|
|
//==============================================================================================
|
|
// Sets up the view position offset.
|
|
{
|
|
AActor* const mo = viewPoint.camera;
|
|
const DViewPosition* const viewOffset = mo->ViewPos;
|
|
DVector3 camPos;
|
|
if (matchPlayer)
|
|
{
|
|
if (usePawn)
|
|
{
|
|
camPos = DVector3(client->Pos().XY(), client->player->viewz);
|
|
const DViewPosition* const pawnVP = client->ViewPos;
|
|
if (pawnVP != nullptr)
|
|
{
|
|
// Add these directly to the view position offset (not 100% accurate but close enough).
|
|
if (pawnVP->Flags & VPSF_ABSOLUTEPOS)
|
|
camPos = pawnVP->Offset;
|
|
else if (pawnVP->Flags & VPSF_ABSOLUTEOFFSET)
|
|
iView->ViewOffset = pawnVP->Offset;
|
|
else
|
|
iView->ViewOffset = DQuaternion::FromAngles(client->Angles.Yaw, client->Angles.Pitch, client->Angles.Roll) * pawnVP->Offset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
camPos = mainView.Pos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
camPos = { mo->Pos().XY(), mo->player != nullptr ? mo->player->viewz : mo->Z() + mo->GetCameraHeight() };
|
|
}
|
|
|
|
viewPoint.showviewer = false;
|
|
viewPoint.bForceNoViewer = matchPlayer;
|
|
|
|
if (player != nullptr && gamestate != GS_TITLELEVEL
|
|
&& ((player->cheats & CF_CHASECAM) || (r_deathcamera && (viewPoint.camera->flags6 & MF6_KILLED))))
|
|
{
|
|
// The cam Actor should probably be visible in third person.
|
|
viewPoint.showviewer = true;
|
|
camPos.Z = mo->Top() - mo->Floorclip;
|
|
iView->ViewOffset.Z = clamp<double>(chase_height, -1000.0, 1000.0);
|
|
iView->RelativeViewOffset.X = -clamp<double>(chase_dist, 0.0, 30000.0);
|
|
}
|
|
|
|
if (viewOffset != nullptr)
|
|
{
|
|
// No chase/death cam, so use the view offset.
|
|
if (!viewPoint.bForceNoViewer)
|
|
viewPoint.bForceNoViewer = (viewOffset->Flags & VPSF_ABSOLUTEPOS) || !viewOffset->Offset.isZero();
|
|
|
|
if (viewOffset->Flags & VPSF_ABSOLUTEPOS)
|
|
{
|
|
iView->New.ViewPos.Zero();
|
|
if (!matchPlayer)
|
|
camPos = viewOffset->Offset;
|
|
}
|
|
else
|
|
{
|
|
iView->New.ViewPos = viewOffset->Offset;
|
|
}
|
|
}
|
|
|
|
viewPoint.ActorPos = iView->New.Pos = camPos;
|
|
viewPoint.sector = viewPoint.ViewLevel->PointInRenderSubsector(camPos)->sector;
|
|
}
|
|
|
|
if (!paused)
|
|
{
|
|
FQuakeJiggers jiggers;
|
|
if (DEarthquake::StaticGetQuakeIntensities(viewPoint.TicFrac, viewPoint.camera, jiggers) > 0)
|
|
{
|
|
const double quakeFactor = r_quakeintensity;
|
|
if (jiggers.RollIntensity || jiggers.RollWave)
|
|
iView->AngleOffsets.Roll = DAngle::fromDeg(QuakePower(quakeFactor, jiggers.RollIntensity, jiggers.RollWave));
|
|
|
|
if (jiggers.RelIntensity.X || jiggers.RelOffset.X)
|
|
iView->RelativeViewOffset.X += QuakePower(quakeFactor, jiggers.RelIntensity.X, jiggers.RelOffset.X);
|
|
if (jiggers.RelIntensity.Y || jiggers.RelOffset.Y)
|
|
iView->RelativeViewOffset.Y += QuakePower(quakeFactor, jiggers.RelIntensity.Y, jiggers.RelOffset.Y);
|
|
if (jiggers.RelIntensity.Z || jiggers.RelOffset.Z)
|
|
iView->RelativeViewOffset.Z += QuakePower(quakeFactor, jiggers.RelIntensity.Z, jiggers.RelOffset.Z);
|
|
|
|
if (jiggers.Intensity.X || jiggers.Offset.X)
|
|
iView->ViewOffset.X += QuakePower(quakeFactor, jiggers.Intensity.X, jiggers.Offset.X);
|
|
if (jiggers.Intensity.Y || jiggers.Offset.Y)
|
|
iView->ViewOffset.Y += QuakePower(quakeFactor, jiggers.Intensity.Y, jiggers.Offset.Y);
|
|
if (jiggers.Intensity.Z || jiggers.Offset.Z)
|
|
iView->ViewOffset.Z += QuakePower(quakeFactor, jiggers.Intensity.Z, jiggers.Offset.Z);
|
|
}
|
|
}
|
|
|
|
// [MR] Apply view angles as the viewpoint angles if asked to do so.
|
|
if (matchPlayer)
|
|
iView->New.Angles = usePawn ? (client->flags8 & MF8_ABSVIEWANGLES ? client->ViewAngles : client->Angles + client->ViewAngles) : mainView.Angles;
|
|
else
|
|
iView->New.Angles = !(viewPoint.camera->flags8 & MF8_ABSVIEWANGLES) ? viewPoint.camera->Angles : viewPoint.camera->ViewAngles;
|
|
|
|
iView->New.ViewAngles = viewPoint.camera->ViewAngles;
|
|
// [MR] Process player angle changes if permitted to do so.
|
|
if (player != nullptr && (player->cheats & CF_SCALEDNOLERP) && P_NoInterpolation(player, viewPoint.camera))
|
|
R_DoActorTickerAngleChanges(player, iView->New.Angles, viewPoint.TicFrac);
|
|
|
|
// If currently tracking the player's real view, don't do any sort of interpolating.
|
|
if (matchPlayer && !usePawn)
|
|
viewPoint.camera->renderflags |= RF_NOINTERPOLATEVIEW;
|
|
|
|
if (viewPoint.camera->player != nullptr)
|
|
player = viewPoint.camera->player;
|
|
|
|
if (iView->prevTic == -1 || r_NoInterpolate || (viewPoint.camera->renderflags & RF_NOINTERPOLATEVIEW))
|
|
{
|
|
viewPoint.camera->renderflags &= ~RF_NOINTERPOLATEVIEW;
|
|
R_ResetViewInterpolation();
|
|
iView->prevTic = curTic;
|
|
}
|
|
|
|
R_InterpolateView(viewPoint, player, viewPoint.TicFrac, iView);
|
|
|
|
viewPoint.SetViewAngle(viewWindow);
|
|
|
|
// Keep the view within the sector's floor and ceiling
|
|
// But allow VPSF_ALLOWOUTOFBOUNDS camera viewpoints to go out of bounds when using hardware renderer
|
|
if (viewPoint.sector->PortalBlocksMovement(sector_t::ceiling) && (!viewPoint.IsAllowedOoB() || !V_IsHardwareRenderer()))
|
|
{
|
|
const double z = viewPoint.sector->ceilingplane.ZatPoint(viewPoint.Pos) - 4.0;
|
|
if (viewPoint.Pos.Z > z)
|
|
viewPoint.Pos.Z = z;
|
|
}
|
|
|
|
if (viewPoint.sector->PortalBlocksMovement(sector_t::floor) && (!viewPoint.IsAllowedOoB() || !V_IsHardwareRenderer()))
|
|
{
|
|
const double z = viewPoint.sector->floorplane.ZatPoint(viewPoint.Pos) + 4.0;
|
|
if (viewPoint.Pos.Z < z)
|
|
viewPoint.Pos.Z = z;
|
|
}
|
|
|
|
viewPoint.extralight = viewPoint.camera->player != nullptr ? viewPoint.camera->player->extralight : 0;
|
|
|
|
// killough 3/20/98, 4/4/98: select colormap based on player status
|
|
// [RH] Can also select a blend
|
|
unsigned int newBlend = 0u;
|
|
const TArray<lightlist_t>& lightlist = viewPoint.sector->e->XFloor.lightlist;
|
|
if (lightlist.Size() > 0)
|
|
{
|
|
for (size_t i = 0u; i < lightlist.Size(); ++i)
|
|
{
|
|
const secplane_t& plane = (i < lightlist.Size() - 1u) ? lightlist[i + 1u].plane : viewPoint.sector->floorplane;
|
|
int viewSide = plane.PointOnSide(viewPoint.Pos);
|
|
|
|
// Reverse the direction of the test if the plane was downward facing.
|
|
// We want to know if the view is above it, whatever its orientation may be.
|
|
if (plane.fC() < 0.0)
|
|
viewSide = -viewSide;
|
|
|
|
if (viewSide > 0)
|
|
{
|
|
// 3d floor 'fog' is rendered as a blending value.
|
|
PalEntry blend = lightlist[i].blend;
|
|
|
|
// If no alpha is set, use 50%.
|
|
if (!blend.a && blend != 0)
|
|
blend.a = 128u;
|
|
|
|
newBlend = blend.d;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const sector_t* const sec = viewPoint.sector->GetHeightSec();
|
|
if (sec != nullptr)
|
|
{
|
|
newBlend = sec->floorplane.PointOnSide(viewPoint.Pos) < 0
|
|
? sec->bottommap
|
|
: sec->ceilingplane.PointOnSide(viewPoint.Pos) < 0
|
|
? sec->topmap
|
|
: sec->midmap;
|
|
|
|
if (APART(newBlend) == 0u && newBlend >= fakecmaps.Size())
|
|
newBlend = 0u;
|
|
}
|
|
}
|
|
|
|
// [RH] Don't override testblend unless entering a sector with a
|
|
// blend different from the previous sector's. Same goes with
|
|
// NormalLight's maps pointer.
|
|
if (R_OldBlend != newBlend)
|
|
R_OldBlend = newBlend;
|
|
|
|
++validcount;
|
|
|
|
if (r_clearbuffer != 0)
|
|
{
|
|
int color = 0;
|
|
int hom = r_clearbuffer;
|
|
if (hom == 3)
|
|
hom = ((screen->FrameTime / 128) & 1) + 1;
|
|
if (hom == 1)
|
|
color = GPalette.BlackIndex;
|
|
else if (hom == 2)
|
|
color = GPalette.WhiteIndex;
|
|
else if (hom == 4)
|
|
color = (screen->FrameTime / 32) & 255;
|
|
else
|
|
color = pr_hom();
|
|
|
|
screen->SetClearColor(color);
|
|
SWRenderer->SetClearColor(color);
|
|
}
|
|
else
|
|
{
|
|
screen->SetClearColor(GPalette.BlackIndex);
|
|
}
|
|
|
|
|
|
// And finally some info that is needed for the hardware renderer
|
|
|
|
// Scale the pitch to account for the pixel stretching, because the playsim doesn't know about this and treats it as 1:1.
|
|
// However, to set up a projection matrix this needs to be adjusted.
|
|
const double radPitch = viewPoint.Angles.Pitch.Normalized180().Radians();
|
|
const double angx = cos(radPitch);
|
|
const double angy = sin(radPitch) * actor->Level->info->pixelstretch;
|
|
const double alen = sqrt(angx*angx + angy*angy);
|
|
|
|
viewPoint.HWAngles.Pitch = FAngle::fromRad((float)asin(angy / alen));
|
|
viewPoint.HWAngles.Roll = FAngle::fromDeg(viewPoint.Angles.Roll.Degrees());
|
|
|
|
// ViewActor only gets set if the camera actor shouldn't be rendered.
|
|
viewPoint.ViewActor = viewPoint.showviewer ? nullptr : actor;
|
|
}
|
|
|
|
|
|
CUSTOM_CVAR(Float, maxviewpitch, 90.f, CVAR_ARCHIVE | CVAR_SERVERINFO)
|
|
{
|
|
if (self>90.f) self = 90.f;
|
|
else if (self<-90.f) self = -90.f;
|
|
if (usergame)
|
|
{
|
|
// [SP] Update pitch limits to the netgame/gamesim.
|
|
players[consoleplayer].SendPitchLimits();
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// R_ShouldDrawSpriteShadow
|
|
//
|
|
//==========================================================================
|
|
|
|
bool R_ShouldDrawSpriteShadow(AActor *thing)
|
|
{
|
|
int rf = thing->renderflags;
|
|
// for wall and flat sprites the shadow math does not work so these must be unconditionally skipped.
|
|
if (rf & (RF_FLATSPRITE | RF_WALLSPRITE)) return false;
|
|
|
|
bool doit = false;
|
|
switch (r_actorspriteshadow)
|
|
{
|
|
case 1:
|
|
doit = (rf & RF_CASTSPRITESHADOW);
|
|
break;
|
|
|
|
case 2:
|
|
doit = (rf & RF_CASTSPRITESHADOW) || (!(rf & RF_NOSPRITESHADOW) && ((thing->flags3 & MF3_ISMONSTER) || thing->player != nullptr));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (doit)
|
|
{
|
|
auto rs = thing->RenderStyle;
|
|
rs.CheckFuzz();
|
|
// For non-standard render styles, draw no shadows. This will always look weird. However, if the sprite forces shadows, render them anyway.
|
|
if (!(rf & RF_CASTSPRITESHADOW))
|
|
{
|
|
if (rs.BlendOp != STYLEOP_Add && rs.BlendOp != STYLEOP_Shadow) return false;
|
|
if (rs.DestAlpha != STYLEALPHA_Zero && rs.DestAlpha != STYLEALPHA_InvSrc) return false;
|
|
}
|
|
}
|
|
return doit;
|
|
|
|
|
|
}
|