From 95b264bdb6158c8e62ec2ded6c1e5ee7c0719c6c Mon Sep 17 00:00:00 2001 From: dileepvr Date: Sun, 11 Aug 2024 08:04:40 -0600 Subject: [PATCH] Isometric Mode and Robin (#2618) * Feature-complete isometric mode fork. * Dithered transparency condition tweaks. * Dithered transparency for non-corpse monsters only (and missiles). * SpectatorCamera vertical shift. * Including math.h in hw_sprites.cpp to keep visual studio happy (it couldn't find M_SQRT2 definition). * Defining MY_SQRT2 in hw_sprites.cpp to keep visual studio happy (it couldn't find M_SQRT2 definition). * Defining MY_SQRT2 in r_utility.cpp also to keep visual studio happy. * retrigger checks * Have correct sprite angle-frame face the camera with orthographic projection enabled. * Dithered Transparency now works properly on 3D floors. Moved that dither-trans flag setting code within hw_bsp.cpp to handle double-processing of linedefs. Added helper functions to FRenderViewpoint class 'bool IsOrtho()' and 'bool IsAllowedOoB()' to clean up checks everywhere in the code. * Fixed indents. Added bbox property to subsector struct and use it instead of BSP nodes and Clippers (creating a bbox around viewpoint and checking for overlap) in orthographic mode when no fog of war is active. Turns out to be much faster. Though you need really big maps (Winter's Fury MAP01) to see a difference in fps. * Non-linux checks don't like uint. Changed to unsigned int. * Small change of a float to camera.zs. Ignore for testing. Should make no difference. * Update actor.h to remain mergeable RF2_NOMIPMAP was introduced, so I had to displace RF_ISOMETRICSPRITES to next bit. --- src/am_map.cpp | 4 +- .../rendering/hwrenderer/data/hw_cvars.h | 3 + .../hwrenderer/data/hw_renderstate.h | 2 +- .../hwrenderer/data/hw_shaderpatcher.cpp | 1 + .../rendering/hwrenderer/data/hw_vrmodes.cpp | 20 +- .../rendering/hwrenderer/data/hw_vrmodes.h | 2 +- src/common/utility/m_bbox.h | 7 + src/gamedata/g_mapinfo.cpp | 1 + src/gamedata/g_mapinfo.h | 3 +- src/gamedata/r_defs.h | 4 + src/maploader/maploader.cpp | 19 ++ src/playsim/actor.h | 9 +- src/playsim/p_map.cpp | 34 ++-- src/playsim/p_trace.cpp | 4 + src/playsim/p_trace.h | 1 + src/rendering/hwrenderer/hw_entrypoint.cpp | 6 +- src/rendering/hwrenderer/scene/hw_bsp.cpp | 190 +++++++++++++++++- src/rendering/hwrenderer/scene/hw_clipper.cpp | 144 ++++++++++++- src/rendering/hwrenderer/scene/hw_clipper.h | 18 +- .../hwrenderer/scene/hw_drawinfo.cpp | 118 +++++++++-- src/rendering/hwrenderer/scene/hw_drawinfo.h | 5 + src/rendering/hwrenderer/scene/hw_flats.cpp | 11 +- src/rendering/hwrenderer/scene/hw_portal.cpp | 3 +- src/rendering/hwrenderer/scene/hw_sky.cpp | 1 + src/rendering/hwrenderer/scene/hw_sprites.cpp | 44 +++- src/rendering/hwrenderer/scene/hw_walls.cpp | 8 + src/rendering/r_utility.cpp | 97 ++++++--- src/rendering/r_utility.h | 8 + src/scripting/thingdef_data.cpp | 1 + wadsrc/static/shaders/glsl/main.fp | 38 ++++ wadsrc/static/shaders_gles/glsl/main.fp | 5 + wadsrc/static/zscript/actors/shared/camera.zs | 84 ++++++++ wadsrc/static/zscript/constants.zs | 8 +- 33 files changed, 819 insertions(+), 84 deletions(-) diff --git a/src/am_map.cpp b/src/am_map.cpp index 6ee8165da..c547884b2 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -2816,7 +2816,7 @@ void DAutomap::drawPlayers () int numarrowlines; double vh = players[consoleplayer].viewheight; - DVector2 pos = players[consoleplayer].camera->InterpolatedPosition(r_viewpoint.TicFrac).XY(); + DVector2 pos = players[consoleplayer].mo->InterpolatedPosition(r_viewpoint.TicFrac).XY(); pt.x = pos.X; pt.y = pos.Y; if (am_rotate == 1 || (am_rotate == 2 && viewactive)) @@ -2826,7 +2826,7 @@ void DAutomap::drawPlayers () } else { - angle = players[consoleplayer].camera->InterpolatedAngles(r_viewpoint.TicFrac).Yaw; + angle = players[consoleplayer].mo->InterpolatedAngles(r_viewpoint.TicFrac).Yaw; } if (am_cheat != 0 && CheatMapArrow.Size() > 0) diff --git a/src/common/rendering/hwrenderer/data/hw_cvars.h b/src/common/rendering/hwrenderer/data/hw_cvars.h index cf2364979..7f84ba09e 100644 --- a/src/common/rendering/hwrenderer/data/hw_cvars.h +++ b/src/common/rendering/hwrenderer/data/hw_cvars.h @@ -54,3 +54,6 @@ EXTERN_CVAR(Int, gl_shadowmap_filter) EXTERN_CVAR(Bool, gl_brightfog) EXTERN_CVAR(Bool, gl_lightadditivesurfaces) EXTERN_CVAR(Bool, gl_notexturefill) + +EXTERN_CVAR(Bool, r_radarclipper) +EXTERN_CVAR(Bool, r_dithertransparency) diff --git a/src/common/rendering/hwrenderer/data/hw_renderstate.h b/src/common/rendering/hwrenderer/data/hw_renderstate.h index 05fd6bcbf..41e4b50a3 100644 --- a/src/common/rendering/hwrenderer/data/hw_renderstate.h +++ b/src/common/rendering/hwrenderer/data/hw_renderstate.h @@ -25,7 +25,7 @@ enum ERenderEffect EFF_SPHEREMAP, EFF_BURN, EFF_STENCIL, - + EFF_DITHERTRANS, MAX_EFFECTS }; diff --git a/src/common/rendering/hwrenderer/data/hw_shaderpatcher.cpp b/src/common/rendering/hwrenderer/data/hw_shaderpatcher.cpp index 9049e5090..d32b09e23 100644 --- a/src/common/rendering/hwrenderer/data/hw_shaderpatcher.cpp +++ b/src/common/rendering/hwrenderer/data/hw_shaderpatcher.cpp @@ -299,6 +299,7 @@ const FEffectShader effectshaders[] = { "spheremap", "shaders/glsl/main.vp", "shaders/glsl/main.fp", "shaders/glsl/func_normal.fp", "shaders/glsl/material_normal.fp", "#define SPHEREMAP\n#define NO_ALPHATEST\n" }, { "burn", "shaders/glsl/main.vp", "shaders/glsl/burn.fp", nullptr, nullptr, "#define SIMPLE\n#define NO_ALPHATEST\n" }, { "stencil", "shaders/glsl/main.vp", "shaders/glsl/stencil.fp", nullptr, nullptr, "#define SIMPLE\n#define NO_ALPHATEST\n" }, + { "dithertrans", "shaders/glsl/main.vp", "shaders/glsl/main.fp", "shaders/glsl/func_normal.fp", "shaders/glsl/material_normal.fp", "#define NO_ALPHATEST\n#define DITHERTRANS\n" }, }; int DFrameBuffer::GetShaderCount() diff --git a/src/common/rendering/hwrenderer/data/hw_vrmodes.cpp b/src/common/rendering/hwrenderer/data/hw_vrmodes.cpp index c51ce2ee9..8cb3c94e5 100644 --- a/src/common/rendering/hwrenderer/data/hw_vrmodes.cpp +++ b/src/common/rendering/hwrenderer/data/hw_vrmodes.cpp @@ -146,11 +146,27 @@ float VREyeInfo::getShift() const return vr_swap_eyes ? -res : res; } -VSMatrix VREyeInfo::GetProjection(float fov, float aspectRatio, float fovRatio) const +VSMatrix VREyeInfo::GetProjection(float fov, float aspectRatio, float fovRatio, bool iso_ortho) const { VSMatrix result; - if (mShiftFactor == 0) + if (iso_ortho) // Orthographic projection for isometric viewpoint + { + double zNear = -3.0/fovRatio; // screen->GetZNear(); + double zFar = screen->GetZFar(); + + double fH = tan(DEG2RAD(fov) / 2) / fovRatio; + double fW = fH * aspectRatio * mScaleFactor; + double left = -fW; + double right = fW; + double bottom = -fH; + double top = fH; + + VSMatrix fmat(1); + fmat.ortho((float)left, (float)right, (float)bottom, (float)top, (float)zNear, (float)zFar); + return fmat; + } + else if (mShiftFactor == 0) { float fovy = (float)(2 * RAD2DEG(atan(tan(DEG2RAD(fov) / 2) / fovRatio))); result.perspective(fovy, aspectRatio, screen->GetZNear(), screen->GetZFar()); diff --git a/src/common/rendering/hwrenderer/data/hw_vrmodes.h b/src/common/rendering/hwrenderer/data/hw_vrmodes.h index 26c9fd211..989281520 100644 --- a/src/common/rendering/hwrenderer/data/hw_vrmodes.h +++ b/src/common/rendering/hwrenderer/data/hw_vrmodes.h @@ -27,7 +27,7 @@ struct VREyeInfo float mShiftFactor; float mScaleFactor; - VSMatrix GetProjection(float fov, float aspectRatio, float fovRatio) const; + VSMatrix GetProjection(float fov, float aspectRatio, float fovRatio, bool iso_ortho) const; DVector3 GetViewShift(float yaw) const; private: float getShift() const; diff --git a/src/common/utility/m_bbox.h b/src/common/utility/m_bbox.h index f117c290b..7f622a6ba 100644 --- a/src/common/utility/m_bbox.h +++ b/src/common/utility/m_bbox.h @@ -72,6 +72,13 @@ public: m_Box[BOXTOP] = pos.Y; } + bool CheckOverlap(const FBoundingBox &box2) + { + bool hori = (Left() > box2.Right()) || (Right() < box2.Left()); + bool vert = (Bottom() > box2.Top()) || (Top() < box2.Bottom()); + return !(hori || vert); // [DVR] For alternative space partition + } + inline double Top () const { return m_Box[BOXTOP]; } inline double Bottom () const { return m_Box[BOXBOTTOM]; } inline double Left () const { return m_Box[BOXLEFT]; } diff --git a/src/gamedata/g_mapinfo.cpp b/src/gamedata/g_mapinfo.cpp index 748d7cacd..3d9bc8bd9 100644 --- a/src/gamedata/g_mapinfo.cpp +++ b/src/gamedata/g_mapinfo.cpp @@ -1819,6 +1819,7 @@ MapFlagHandlers[] = { "disableskyboxao", MITYPE_CLRFLAG3, LEVEL3_SKYBOXAO, 0 }, { "avoidmelee", MITYPE_SETFLAG3, LEVEL3_AVOIDMELEE, 0 }, { "attenuatelights", MITYPE_SETFLAG3, LEVEL3_ATTENUATE, 0 }, + { "nofogofwar", MITYPE_SETFLAG3, LEVEL3_NOFOGOFWAR, 0 }, { "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes { "nopassover", MITYPE_COMPATFLAG, COMPATF_NO_PASSMOBJ, 0 }, { "passover", MITYPE_CLRCOMPATFLAG, COMPATF_NO_PASSMOBJ, 0 }, diff --git a/src/gamedata/g_mapinfo.h b/src/gamedata/g_mapinfo.h index b56d3b632..73b46d7e0 100644 --- a/src/gamedata/g_mapinfo.h +++ b/src/gamedata/g_mapinfo.h @@ -269,7 +269,8 @@ enum ELevelFlags : unsigned int LEVEL3_NOSHADOWMAP = 0x00010000, // disables shadowmaps for a given level. LEVEL3_AVOIDMELEE = 0x00020000, // global flag needed for proper MBF support. LEVEL3_NOJUMPDOWN = 0x00040000, // only for MBF21. Inverse of MBF's dog_jumping flag. - LEVEL3_LIGHTCREATED = 0x00080000, // a light had been created in the last frame + LEVEL3_LIGHTCREATED = 0x00080000, // a light had been created in the last frame + LEVEL3_NOFOGOFWAR = 0x00100000, // disables effect of r_radarclipper CVAR on this map }; diff --git a/src/gamedata/r_defs.h b/src/gamedata/r_defs.h index 3333a0c7a..943893c66 100644 --- a/src/gamedata/r_defs.h +++ b/src/gamedata/r_defs.h @@ -303,6 +303,7 @@ struct secplane_t DVector3 normal; double D, negiC; // negative iC because that also saves a negation in all methods using this. public: + bool dithertransflag; // Render plane with dithering transparency shader (gets reset every frame) friend FSerializer &Serialize(FSerializer &arc, const char *key, secplane_t &p, secplane_t *def); void set(double aa, double bb, double cc, double dd) @@ -1179,6 +1180,8 @@ enum WALLF_ABSLIGHTING_TOP = WALLF_ABSLIGHTING_TIER << 0, // Top tier light is absolute instead of relative WALLF_ABSLIGHTING_MID = WALLF_ABSLIGHTING_TIER << 1, // Mid tier light is absolute instead of relative WALLF_ABSLIGHTING_BOTTOM = WALLF_ABSLIGHTING_TIER << 2, // Bottom tier light is absolute instead of relative + + WALLF_DITHERTRANS = 8192, // Render with dithering transparency shader (gets reset every frame) }; struct side_t @@ -1654,6 +1657,7 @@ struct subsector_t uint32_t numlines; uint16_t flags; short mapsection; + FBoundingBox bbox; // [DVR] For alternative space culling in orthographic projection with no fog of war // subsector related GL data int validcount; diff --git a/src/maploader/maploader.cpp b/src/maploader/maploader.cpp index 826afc85f..fe050d13c 100644 --- a/src/maploader/maploader.cpp +++ b/src/maploader/maploader.cpp @@ -3262,6 +3262,25 @@ void MapLoader::LoadLevel(MapData *map, const char *lumpname, int position) Level->aabbTree = new DoomLevelAABBTree(Level); Level->levelMesh = new DoomLevelMesh(*Level); + + // [DVR] Populate subsector->bbox for alternative space culling in orthographic projection with no fog of war + subsector_t* sub = &Level->subsectors[0]; + seg_t* seg; + for (unsigned int kk = 0; kk < Level->subsectors.Size(); kk++) + { + sub[kk].bbox.ClearBox(); + unsigned int count = sub[kk].numlines; + seg = sub[kk].firstline; + while(count--) + { + if((seg->v1 != nullptr) && (seg->v2 != nullptr)) + { + sub[kk].bbox.AddToBox(seg->v1->fPos()); + sub[kk].bbox.AddToBox(seg->v2->fPos()); + } + seg++; + } + } } //========================================================================== diff --git a/src/playsim/actor.h b/src/playsim/actor.h index fa0e23bf2..745c4be9d 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -502,6 +502,7 @@ enum ActorRenderFlag2 RF2_FLIPSPRITEOFFSETY = 0x0020, RF2_CAMFOLLOWSPLAYER = 0x0040, // Matches the cam's base position and angles to the main viewpoint. RF2_NOMIPMAP = 0x0080, // [Nash] forces no mipmapping on sprites. Useful for tiny sprites that need to remain visually crisp + RF2_ISOMETRICSPRITES = 0x0100, }; // This translucency value produces the closest match to Heretic's TINTTAB. @@ -692,8 +693,10 @@ struct FDropItem enum EViewPosFlags // [MC] Flags for SetViewPos. { - VPSF_ABSOLUTEOFFSET = 1 << 1, // Don't include angles. - VPSF_ABSOLUTEPOS = 1 << 2, // Use absolute position. + VPSF_ABSOLUTEOFFSET = 1 << 1, // Don't include angles. + VPSF_ABSOLUTEPOS = 1 << 2, // Use absolute position. + VPSF_ALLOWOUTOFBOUNDS = 1 << 3, // Allow viewpoint to go out of bounds (hardware renderer only). + VPSF_ORTHOGRAPHIC = 1 << 4, // Use orthographic projection (hardware renderer only). }; enum EAnimOverrideFlags @@ -1123,6 +1126,8 @@ public: DAngle SpriteAngle; DAngle SpriteRotation; DVector2 AutomapOffsets; // Offset the actors' sprite view on the automap by these coordinates. + float isoscaleY; // Y-scale to compensate for Y-billboarding for isometric sprites + float isotheta; // Rotation angle to compensate for Y-billboarding for isometric sprites DRotator Angles; DRotator ViewAngles; // Angle offsets for cameras TObjPtr ViewPos; // Position offsets for cameras diff --git a/src/playsim/p_map.cpp b/src/playsim/p_map.cpp index a79f912ae..af8df92a7 100644 --- a/src/playsim/p_map.cpp +++ b/src/playsim/p_map.cpp @@ -82,6 +82,7 @@ #include "p_blockmap.h" #include "p_3dmidtex.h" #include "vm.h" +#include "d_main.h" #include "decallib.h" @@ -5577,33 +5578,42 @@ void R_OffsetView(FRenderViewpoint& viewPoint, const DVector3& dir, const double { const DAngle baseYaw = dir.Angle(); FTraceResults trace = {}; - if (Trace(viewPoint.Pos, viewPoint.sector, dir, distance, 0u, 0u, nullptr, trace)) + if (viewPoint.IsAllowedOoB() && V_IsHardwareRenderer()) + { + viewPoint.Pos += dir * distance; + viewPoint.sector = viewPoint.ViewLevel->PointInRenderSubsector(viewPoint.Pos)->sector; + } + else if (Trace(viewPoint.Pos, viewPoint.sector, dir, distance, 0u, 0u, nullptr, trace)) { viewPoint.Pos = trace.HitPos - trace.HitVector * min(5.0, trace.Distance); viewPoint.sector = viewPoint.ViewLevel->PointInRenderSubsector(viewPoint.Pos)->sector; + viewPoint.Angles.Yaw += deltaangle(baseYaw, trace.SrcAngleFromTarget); } else { viewPoint.Pos = trace.HitPos; viewPoint.sector = trace.Sector; + viewPoint.Angles.Yaw += deltaangle(baseYaw, trace.SrcAngleFromTarget); } - viewPoint.Angles.Yaw += deltaangle(baseYaw, trace.SrcAngleFromTarget); // TODO: Why does this even need to be done? Please fix tracers already. - if (dir.Z < 0.0) + if (!viewPoint.IsAllowedOoB() || !V_IsHardwareRenderer()) { - while (!viewPoint.sector->PortalBlocksMovement(sector_t::floor) && viewPoint.Pos.Z < viewPoint.sector->GetPortalPlaneZ(sector_t::floor)) + if (dir.Z < 0.0) { - viewPoint.Pos += viewPoint.sector->GetPortalDisplacement(sector_t::floor); - viewPoint.sector = viewPoint.sector->GetPortal(sector_t::floor)->mDestination; + while (!viewPoint.sector->PortalBlocksMovement(sector_t::floor) && viewPoint.Pos.Z < viewPoint.sector->GetPortalPlaneZ(sector_t::floor)) + { + viewPoint.Pos += viewPoint.sector->GetPortalDisplacement(sector_t::floor); + viewPoint.sector = viewPoint.sector->GetPortal(sector_t::floor)->mDestination; + } } - } - else if (dir.Z > 0.0) - { - while (!viewPoint.sector->PortalBlocksMovement(sector_t::ceiling) && viewPoint.Pos.Z > viewPoint.sector->GetPortalPlaneZ(sector_t::ceiling)) + else if (dir.Z > 0.0) { - viewPoint.Pos += viewPoint.sector->GetPortalDisplacement(sector_t::ceiling); - viewPoint.sector = viewPoint.sector->GetPortal(sector_t::ceiling)->mDestination; + while (!viewPoint.sector->PortalBlocksMovement(sector_t::ceiling) && viewPoint.Pos.Z > viewPoint.sector->GetPortalPlaneZ(sector_t::ceiling)) + { + viewPoint.Pos += viewPoint.sector->GetPortalDisplacement(sector_t::ceiling); + viewPoint.sector = viewPoint.sector->GetPortal(sector_t::ceiling)->mDestination; + } } } } diff --git a/src/playsim/p_trace.cpp b/src/playsim/p_trace.cpp index 7e47a7323..ed37d5e34 100644 --- a/src/playsim/p_trace.cpp +++ b/src/playsim/p_trace.cpp @@ -633,6 +633,9 @@ cont: case TRACE_Stop: return false; + case TRACE_ContinueOutOfBounds: + return true; + case TRACE_Abort: Results->HitType = TRACE_HitNone; return false; @@ -732,6 +735,7 @@ bool FTraceInfo::ThingCheck(intercept_t *in, double dist, DVector3 hit) switch (TraceCallback(*Results, TraceCallbackData)) { case TRACE_Continue: return true; + case TRACE_ContinueOutOfBounds: return true; case TRACE_Stop: return false; case TRACE_Abort: Results->HitType = TRACE_HitNone; return false; case TRACE_Skip: Results->HitType = TRACE_HitNone; return true; diff --git a/src/playsim/p_trace.h b/src/playsim/p_trace.h index ef5318678..9035ae965 100644 --- a/src/playsim/p_trace.h +++ b/src/playsim/p_trace.h @@ -109,6 +109,7 @@ enum ETraceStatus TRACE_Continue, // continue the trace, returning this hit if there are none further along TRACE_Skip, // continue the trace; do not return this hit TRACE_Abort, // stop the trace, returning no hits + TRACE_ContinueOutOfBounds, // continue the trace through walls; don't use this for railguns }; bool Trace(const DVector3 &start, sector_t *sector, const DVector3 &direction, double maxDist, diff --git a/src/rendering/hwrenderer/hw_entrypoint.cpp b/src/rendering/hwrenderer/hw_entrypoint.cpp index e80d7ef66..bfa0d8b1c 100644 --- a/src/rendering/hwrenderer/hw_entrypoint.cpp +++ b/src/rendering/hwrenderer/hw_entrypoint.cpp @@ -163,7 +163,11 @@ sector_t* RenderViewpoint(FRenderViewpoint& mainvp, AActor* camera, IntRect* bou di->Viewpoint.FieldOfView = DAngle::fromDeg(fov); // Set the real FOV for the current scene (it's not necessarily the same as the global setting in r_viewpoint) // Stereo mode specific perspective projection - di->VPUniforms.mProjectionMatrix = eye.GetProjection(fov, ratio, fovratio); + float inv_iso_dist = 1.0f; + bool iso_ortho = (camera->ViewPos != NULL) && (camera->ViewPos->Flags & VPSF_ORTHOGRAPHIC); + if (iso_ortho && (camera->ViewPos->Offset.Length() > 0)) inv_iso_dist = 1.0/camera->ViewPos->Offset.Length(); + di->VPUniforms.mProjectionMatrix = eye.GetProjection(fov, ratio, fovratio * inv_iso_dist, iso_ortho); + // Stereo mode specific viewpoint adjustment vp.Pos += eye.GetViewShift(vp.HWAngles.Yaw.Degrees()); di->SetupView(RenderState, vp.Pos.X, vp.Pos.Y, vp.Pos.Z, false, false); diff --git a/src/rendering/hwrenderer/scene/hw_bsp.cpp b/src/rendering/hwrenderer/scene/hw_bsp.cpp index 12439669c..fafa680d4 100644 --- a/src/rendering/hwrenderer/scene/hw_bsp.cpp +++ b/src/rendering/hwrenderer/scene/hw_bsp.cpp @@ -51,11 +51,24 @@ CVAR(Bool, gl_multithread, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) EXTERN_CVAR(Float, r_actorspriteshadowdist) +EXTERN_CVAR(Bool, r_radarclipper) +EXTERN_CVAR(Bool, r_dithertransparency) thread_local bool isWorkerThread; ctpl::thread_pool renderPool(1); bool inited = false; +const int MAXDITHERACTORS = 20; // Maximum number of enemies that can set dither-transparency flags +AActor* RenderedTargets[MAXDITHERACTORS]; +int RTnum; + +void ClearDitherTargets() +{ + RTnum = 0; // Number of rendered enemies/targets + for (int ii = 0; ii < MAXDITHERACTORS; ii++) + RenderedTargets[ii] = nullptr; +} + struct RenderJob { enum @@ -269,6 +282,20 @@ void HWDrawInfo::AddLine (seg_t *seg, bool portalclip) auto &clipper = *mClipper; angle_t startAngle = clipper.GetClipAngle(seg->v2); angle_t endAngle = clipper.GetClipAngle(seg->v1); + auto &clipperr = *rClipper; + angle_t startAngleR = clipperr.PointToPseudoAngle(seg->v2->fX(), seg->v2->fY()); + angle_t endAngleR = clipperr.PointToPseudoAngle(seg->v1->fX(), seg->v1->fY()); + + if(r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && (startAngleR - endAngleR >= ANGLE_180)) + { + if (!seg->backsector) clipperr.SafeAddClipRange(startAngleR, endAngleR); + else if((seg->sidedef != nullptr) && !uint8_t(seg->sidedef->Flags & WALLF_POLYOBJ) && (currentsector->sectornum != seg->backsector->sectornum)) + { + if (in_area == area_default) in_area = hw_CheckViewArea(seg->v2, seg->v1, seg->frontsector, seg->backsector); + backsector = hw_FakeFlat(seg->backsector, in_area, true); + if (hw_CheckClip(seg->sidedef, currentsector, backsector)) clipperr.SafeAddClipRange(startAngleR, endAngleR); + } + } // Back side, i.e. backface culling - read: endAngle >= startAngle! if (startAngle-endAngleflags & SSECMF_DRAWN)) { - if (clipper.SafeCheckRange(startAngle, endAngle)) + if (clipper.SafeCheckRange(startAngle, endAngle) && (!r_radarclipper || (Level->flags3 & LEVEL3_NOFOGOFWAR))) { - currentsubsector->flags |= SSECMF_DRAWN; + currentsubsector->flags |= SSECMF_DRAWN; + } + if ((r_radarclipper || !(Level->flags3 & LEVEL3_NOFOGOFWAR)) && clipperr.SafeCheckRange(startAngleR, endAngleR)) + { + currentsubsector->flags |= SSECMF_DRAWN; } } return; } - if (!clipper.SafeCheckRange(startAngle, endAngle)) + if (!clipper.SafeCheckRange(startAngle, endAngle)) { return; } - currentsubsector->flags |= SSECMF_DRAWN; + + auto &clipperv = *vClipper; + angle_t startPitch = clipperv.PointToPseudoPitch(seg->v1->fX(), seg->v1->fY(), currentsector->floorplane.ZatPoint(seg->v1)); + angle_t endPitch = clipperv.PointToPseudoPitch(seg->v1->fX(), seg->v1->fY(), currentsector->ceilingplane.ZatPoint(seg->v1)); + angle_t startPitch2 = clipperv.PointToPseudoPitch(seg->v2->fX(), seg->v2->fY(), currentsector->floorplane.ZatPoint(seg->v2)); + angle_t endPitch2 = clipperv.PointToPseudoPitch(seg->v2->fX(), seg->v2->fY(), currentsector->ceilingplane.ZatPoint(seg->v2)); + angle_t temp; + // Wall can be tilted from viewpoint perspective. Find vertical extent on screen in psuedopitch units (0 to 2, bottom to top) + if(int(startPitch) > int(startPitch2)) // Handle zero crossing + { + temp = startPitch; startPitch = startPitch2; startPitch2 = temp; // exchange + } + if(int(endPitch) > int(endPitch2)) // Handle zero crossing + { + temp = endPitch; endPitch = endPitch2; endPitch2 = temp; // exchange + } + + if (!clipperv.SafeCheckRange(startPitch, endPitch2)) + { + return; + } + + if (!r_radarclipper || (Level->flags3 & LEVEL3_NOFOGOFWAR) || clipperr.SafeCheckRange(startAngleR, endAngleR)) + currentsubsector->flags |= SSECMF_DRAWN; uint8_t ispoly = uint8_t(seg->sidedef->Flags & WALLF_POLYOBJ); if (!seg->backsector) { - clipper.SafeAddClipRange(startAngle, endAngle); + if(!Viewpoint.IsAllowedOoB()) + if (!(seg->sidedef->Flags & WALLF_DITHERTRANS)) clipper.SafeAddClipRange(startAngle, endAngle); } else if (!ispoly) // Two-sided polyobjects never obstruct the view { @@ -325,7 +380,8 @@ void HWDrawInfo::AddLine (seg_t *seg, bool portalclip) if (hw_CheckClip(seg->sidedef, currentsector, backsector)) { - clipper.SafeAddClipRange(startAngle, endAngle); + if(!Viewpoint.IsAllowedOoB() && !(seg->sidedef->Flags & WALLF_DITHERTRANS)) + clipper.SafeAddClipRange(startAngle, endAngle); } } } @@ -526,13 +582,14 @@ void HWDrawInfo::RenderThings(subsector_t * sub, sector_t * sector) { sector_t * sec=sub->sector; // Handle all things in sector. - const auto &vp = Viewpoint; + const auto &vp = Viewpoint; for (auto p = sec->touching_renderthings; p != nullptr; p = p->m_snext) { auto thing = p->m_thing; if (thing->validcount == validcount) continue; thing->validcount = validcount; + if(Viewpoint.IsAllowedOoB() && thing->Sector->isSecret() && thing->Sector->wasSecret() && !r_radarclipper) continue; // This covers things that are touching non-secret sectors FIntCVar *cvar = thing->GetInfo()->distancecheck; if (cvar != nullptr && *cvar >= 0) { @@ -666,6 +723,51 @@ void HWDrawInfo::DoSubsector(subsector_t * sub) fakesector=hw_FakeFlat(sector, in_area, false); + if(Viewpoint.IsAllowedOoB() && sector->isSecret() && sector->wasSecret() && !r_radarclipper) return; + + // cull everything if subsector outside vertical clipper + if ((sub->polys == nullptr) && (!Viewpoint.IsOrtho() || !((Level->flags3 & LEVEL3_NOFOGOFWAR) || !r_radarclipper))) + { + auto &clipper = *mClipper; + auto &clipperv = *vClipper; + auto &clipperr = *rClipper; + int count = sub->numlines; + seg_t * seg = sub->firstline; + bool anglevisible = false; + bool pitchvisible = false; + bool radarvisible = false; + angle_t pitchtemp; + angle_t pitchmin = ANGLE_90; + angle_t pitchmax = 0; + + while (count--) + { + if((seg->v1 != nullptr) && (seg->v2 != nullptr)) + { + angle_t startAngle = clipper.GetClipAngle(seg->v2); + angle_t endAngle = clipper.GetClipAngle(seg->v1); + if (startAngle-endAngle >= ANGLE_180) anglevisible |= clipper.SafeCheckRange(startAngle, endAngle); + angle_t startAngleR = clipperr.PointToPseudoAngle(seg->v2->fX(), seg->v2->fY()); + angle_t endAngleR = clipperr.PointToPseudoAngle(seg->v1->fX(), seg->v1->fY()); + if (startAngleR-endAngleR >= ANGLE_180) + radarvisible |= (clipperr.SafeCheckRange(startAngleR, endAngleR) || (Level->flags3 & LEVEL3_NOFOGOFWAR) || ((sub->flags & SSECMF_DRAWN) && !deathmatch)); + pitchmin = clipperv.PointToPseudoPitch(seg->v1->fX(), seg->v1->fY(), sector->floorplane.ZatPoint(seg->v1)); + pitchmax = clipperv.PointToPseudoPitch(seg->v1->fX(), seg->v1->fY(), sector->ceilingplane.ZatPoint(seg->v1)); + pitchvisible |= clipperv.SafeCheckRange(pitchmin, pitchmax); + if (pitchvisible && anglevisible && radarvisible) break; + pitchtemp = clipperv.PointToPseudoPitch(seg->v2->fX(), seg->v2->fY(), sector->floorplane.ZatPoint(seg->v2)); + if (int(pitchmin) > int(pitchtemp)) pitchmin = pitchtemp; + pitchtemp = clipperv.PointToPseudoPitch(seg->v2->fX(), seg->v2->fY(), sector->ceilingplane.ZatPoint(seg->v2)); + if (int(pitchmax) < int(pitchtemp)) pitchmax = pitchtemp; + pitchvisible |= clipperv.SafeCheckRange(pitchmin, pitchmax); + if (pitchvisible && anglevisible && radarvisible) break; + } + seg++; + } + // Skip subsector if outside vertical or horizontal clippers or is in unexplored territory (fog of war) + if(!pitchvisible || !anglevisible || (!radarvisible && r_radarclipper)) return; + } + if (mClipPortal) { int clipres = mClipPortal->ClipSubsector(sub); @@ -723,6 +825,20 @@ void HWDrawInfo::DoSubsector(subsector_t * sub) SetupSprite.Unclock(); } } + if (r_dithertransparency && Viewpoint.IsAllowedOoB() && (RTnum < MAXDITHERACTORS)) + { + // [DVR] Not parallelizable due to variables RTnum and RenderedTargets[] + for (auto p = sector->touching_renderthings; p != nullptr; p = p->m_snext) + { + auto thing = p->m_thing; + if (thing->validcount == validcount) continue; // Don't double count + if (((thing->flags3 & MF3_ISMONSTER) && !(thing->flags & MF_CORPSE)) || (thing->flags & MF_MISSILE)) + { + if (RTnum < MAXDITHERACTORS) RenderedTargets[RTnum++] = thing; + else break; + } + } + } } if (gl_render_flats) @@ -843,19 +959,62 @@ void HWDrawInfo::RenderBSPNode (void *node) if (!(no_renderflags[bsp->Index()] & SSRF_SEEN)) return; } + if (Viewpoint.IsOrtho()) + { + if (!vClipper->CheckBoxOrthoPitch(bsp->bbox[side])) + { + if (!(no_renderflags[bsp->Index()] & SSRF_SEEN)) + return; + } + } node = bsp->children[side]; } DoSubsector ((subsector_t *)((uint8_t *)node - 1)); } +// No need for clipping inside frustum if no fog of war (How is this faster!) +void HWDrawInfo::RenderOrthoNoFog() +{ + if (Viewpoint.IsOrtho() && ((Level->flags3 & LEVEL3_NOFOGOFWAR) || !r_radarclipper)) + { + double vxdbl = Viewpoint.camera->X(); + double vydbl = Viewpoint.camera->Y(); + double ext = Viewpoint.camera->ViewPos->Offset.Length() ? + 3.0 * Viewpoint.camera->ViewPos->Offset.Length() : 100.0; + FBoundingBox viewbox(vxdbl, vydbl, ext); + + for (unsigned int kk = 0; kk < Level->subsectors.Size(); kk++) + { + if (Level->subsectors[kk].bbox.CheckOverlap(viewbox)) + { + DoSubsector (&Level->subsectors[kk]); + } + } + } +} + void HWDrawInfo::RenderBSP(void *node, bool drawpsprites) { + ClearDitherTargets(); Bsp.Clock(); // Give the DrawInfo the viewpoint in fixed point because that's what the nodes are. viewx = FLOAT2FIXED(Viewpoint.Pos.X); viewy = FLOAT2FIXED(Viewpoint.Pos.Y); + if (r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && Viewpoint.IsAllowedOoB() && (Viewpoint.camera->ViewPos->Flags & VPSF_ABSOLUTEOFFSET)) + { + if (Viewpoint.camera->tracer != NULL) + { + viewx = FLOAT2FIXED(Viewpoint.camera->tracer->X()); + viewy = FLOAT2FIXED(Viewpoint.camera->tracer->Y()); + } + else + { + viewx = FLOAT2FIXED(Viewpoint.camera->X()); + viewy = FLOAT2FIXED(Viewpoint.camera->Y()); + } + } validcount++; // used for processing sidedefs only once by the renderer. @@ -866,7 +1025,8 @@ void HWDrawInfo::RenderBSP(void *node, bool drawpsprites) auto future = renderPool.push([&](int id) { WorkerThread(); }); - RenderBSPNode(node); + if (Viewpoint.IsOrtho() && ((Level->flags3 & LEVEL3_NOFOGOFWAR) || !r_radarclipper)) RenderOrthoNoFog(); + else RenderBSPNode(node); jobQueue.AddJob(RenderJob::TerminateJob, nullptr, nullptr); Bsp.Unclock(); @@ -876,9 +1036,21 @@ void HWDrawInfo::RenderBSP(void *node, bool drawpsprites) } else { - RenderBSPNode(node); + if (Viewpoint.IsOrtho() && ((Level->flags3 & LEVEL3_NOFOGOFWAR) || !r_radarclipper)) RenderOrthoNoFog(); + else RenderBSPNode(node); Bsp.Unclock(); } + + // Make rendered targets set dither transparency flags on level geometry for next pass + // Can't do this inside DoSubsector() because both Trace() and P_CheckSight() affect 'validcount' global variable + for (int ii = 0; ii < MAXDITHERACTORS; ii++) + { + if ( RenderedTargets[ii] && P_CheckSight(players[consoleplayer].mo, RenderedTargets[ii], 0) ) + { + SetDitherTransFlags(RenderedTargets[ii]); + } + } + // Process all the sprites on the current portal's back side which touch the portal. if (mCurrentPortal != nullptr) mCurrentPortal->RenderAttached(this); diff --git a/src/rendering/hwrenderer/scene/hw_clipper.cpp b/src/rendering/hwrenderer/scene/hw_clipper.cpp index fd69e267c..94713f107 100644 --- a/src/rendering/hwrenderer/scene/hw_clipper.cpp +++ b/src/rendering/hwrenderer/scene/hw_clipper.cpp @@ -193,7 +193,7 @@ void Clipper::AddClipRange(angle_t start, angle_t end) if (node->end < end) { - node->end = end; + node->end = end; // [DVR] This never triggers because of previous while loop. Remove? } ClipNode *node2 = node->next; @@ -361,6 +361,17 @@ angle_t Clipper::AngleToPseudo(angle_t ang) return xs_Fix<30>::ToFix(result); } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +angle_t Clipper::PitchToPseudo(double ang) +{ + return AngleToPseudo(DAngle::fromDeg(90.0-ang).BAMs()); // Pitch is positive when looking down +} + //----------------------------------------------------------------------------- // // ! Returns the pseudoangle between the line p1 to (infinity, p1.y) and the @@ -378,11 +389,28 @@ angle_t Clipper::PointToPseudoAngle(double x, double y) { double vecx = x - viewpoint->Pos.X; double vecy = y - viewpoint->Pos.Y; + if ((viewpoint->camera != NULL) && amRadar) + { + if (viewpoint->camera->tracer != NULL) + { + vecx = x - viewpoint->camera->tracer->X(); + vecy = y - viewpoint->camera->tracer->Y(); + } + else + { + vecx = x - viewpoint->camera->X(); + vecy = y - viewpoint->camera->Y(); + } + } if (vecx == 0 && vecy == 0) { return 0; } + else if (!amRadar && viewpoint->IsOrtho()) + { + return PointToPseudoOrthoAngle(x, y); + } else { double result = vecy / (fabs(vecx) + fabs(vecy)); @@ -395,6 +423,78 @@ angle_t Clipper::PointToPseudoAngle(double x, double y) } +angle_t Clipper::PointToPseudoPitch(double x, double y, double z) +{ + double vecx = x - viewpoint->Pos.X; + double vecy = y - viewpoint->Pos.Y; + double vecz = z - viewpoint->Pos.Z; + double result = 0; + + if (vecx == 0 && vecy == 0 && vecz == 0) + { + return 0; + } + else if (viewpoint->IsOrtho()) + { + return PointToPseudoOrthoPitch(x, y, z); + } + else + { + double result = vecz / (g_sqrt(vecx*vecx + vecy*vecy) + fabs(vecz)); // -ffast-math compile flag applies to this file, yes? + if ((vecx * viewpoint->TanCos + vecy * viewpoint->TanSin) <= 0.0) // Point is behind viewpoint + { + result = 2.0 - result; + } + return xs_Fix<30>::ToFix(result + 1.0); // range to 0 to 2 to 4 (bottom to top to suplex) + } +} + + +angle_t Clipper::PointToPseudoOrthoAngle(double x, double y) +{ + DVector3 disp = DVector3( x, y, 0 ) - viewpoint->camera->Pos(); + if (viewpoint->camera->ViewPos->Offset.XY().Length() == 0) + { + return AngleToPseudo( viewpoint->Angles.Yaw.BAMs() ); + } + else + { + angle_t af = viewpoint->FrustAngle; + double xproj = disp.XY().Length() * deltaangle(disp.Angle(), viewpoint->Angles.Yaw).Sin(); + xproj *= viewpoint->ScreenProj; + if (fabs(xproj) < 2.0) + { + return AngleToPseudo( viewpoint->Angles.Yaw.BAMs() - xproj * 0.5 * af ); + } + else + { + return (xproj > 0.0 ? AngleToPseudo( viewpoint->Angles.Yaw.BAMs() - af ) : AngleToPseudo( viewpoint->Angles.Yaw.BAMs() + af )); + } + } +} + + +angle_t Clipper::PointToPseudoOrthoPitch(double x, double y, double z) +{ + DVector3 disp = DVector3( x, y, z ) - viewpoint->camera->Pos(); + if (viewpoint->camera->ViewPos->Offset.XY().Length() > 0) + { + double yproj = viewpoint->PitchSin * disp.XY().Length() * deltaangle(disp.Angle(), viewpoint->Angles.Yaw).Cos(); + yproj += viewpoint->PitchCos * disp.Z; + yproj *= viewpoint->ScreenProj; + if (fabs(yproj) <= 1.5) + { + return PitchToPseudo(viewpoint->Angles.Pitch.Degrees() - yproj * 0.5 * viewpoint->FieldOfView.Degrees() ); + } + else + { + double a2 = 0.75*viewpoint->FieldOfView.Degrees(); + a2 *= ( yproj > 0.0 ? -1.0 : 1.0 ); + return PitchToPseudo(viewpoint->Angles.Pitch.Degrees() + a2 ); + } + } + else return PitchToPseudo(viewpoint->Angles.Pitch.Degrees()); +} //----------------------------------------------------------------------------- // @@ -428,7 +528,7 @@ bool Clipper::CheckBox(const float *bspcoord) // Find the corners of the box // that define the edges from current viewpoint. - auto &vp = viewpoint; + auto &vp = viewpoint; boxpos = (vp->Pos.X <= bspcoord[BOXLEFT] ? 0 : vp->Pos.X < bspcoord[BOXRIGHT ] ? 1 : 2) + (vp->Pos.Y >= bspcoord[BOXTOP ] ? 0 : vp->Pos.Y > bspcoord[BOXBOTTOM] ? 4 : 8); @@ -437,7 +537,47 @@ bool Clipper::CheckBox(const float *bspcoord) check = checkcoord[boxpos]; angle1 = PointToPseudoAngle (bspcoord[check[0]], bspcoord[check[1]]); angle2 = PointToPseudoAngle (bspcoord[check[2]], bspcoord[check[3]]); + + if (vp->IsOrtho()) + { + if (angle2 != angle1) return true; + switch (boxpos) // Check if the closer corner is poking into the view area + { + case 0: + case 10: + if ( angle1 != PointToPseudoAngle (bspcoord[check[2]], bspcoord[check[1]]) ) return true; + break; + case 2: + case 8: + if ( angle1 != PointToPseudoAngle (bspcoord[check[0]], bspcoord[check[3]]) ) return true; + break; + default: + break; + } + } return SafeCheckRange(angle2, angle1); } +bool Clipper::CheckBoxOrthoPitch(const float *bspcoord) +{ + angle_t pitchmin, pitchmax; + auto &vp = viewpoint; + if (!vp->IsOrtho()) return true; + + angle_t pitchtemp; + double padding = 1.0/viewpoint->ScreenProj/viewpoint->PitchCos; + double camz = vp->camera->Pos().Z - padding; + pitchmin = PointToPseudoPitch (bspcoord[BOXLEFT], bspcoord[BOXTOP], camz); + pitchmax = PointToPseudoPitch (bspcoord[BOXLEFT], bspcoord[BOXTOP], camz + 2.0*padding); + for (int yi = BOXTOP; yi <= BOXBOTTOM; yi++) + for (int xi = BOXLEFT; xi <= BOXRIGHT; xi++) + { + pitchtemp = PointToPseudoPitch (bspcoord[xi], bspcoord[yi], camz); + if (pitchmin - pitchtemp < ANGLE_180) pitchmin = pitchtemp; + pitchtemp = PointToPseudoPitch (bspcoord[xi], bspcoord[yi], camz + 2.0*padding); + if (pitchtemp - pitchmax < ANGLE_180) pitchmax = pitchtemp; + } + + return (pitchmax != pitchmin); // SafeCheckRange(pitchmin, pitchmax); +} diff --git a/src/rendering/hwrenderer/scene/hw_clipper.h b/src/rendering/hwrenderer/scene/hw_clipper.h index e8da8172c..c2643efd3 100644 --- a/src/rendering/hwrenderer/scene/hw_clipper.h +++ b/src/rendering/hwrenderer/scene/hw_clipper.h @@ -29,10 +29,11 @@ class Clipper ClipNode * clipnodes = nullptr; ClipNode * cliphead = nullptr; ClipNode * silhouette = nullptr; // will be preserved even when RemoveClipRange is called - const FRenderViewpoint *viewpoint = nullptr; + FRenderViewpoint *viewpoint = nullptr; bool blocked = false; static angle_t AngleToPseudo(angle_t ang); + static angle_t PitchToPseudo(double ang); bool IsRangeVisible(angle_t startangle, angle_t endangle); void RemoveRange(ClipNode * cn); void AddClipRange(angle_t startangle, angle_t endangle); @@ -41,6 +42,7 @@ class Clipper public: + bool amRadar = false; Clipper(); void Clear(); @@ -72,7 +74,7 @@ public: return c; } - void SetViewpoint(const FRenderViewpoint &vp) + void SetViewpoint(FRenderViewpoint &vp) { viewpoint = &vp; } @@ -116,6 +118,10 @@ public: SafeAddClipRange(AngleToPseudo(startangle), AngleToPseudo(endangle)); } + void SafeAddClipRangeDegPitches(double startpitch, double endpitch) + { + SafeAddClipRange(PitchToPseudo(startpitch), PitchToPseudo(endpitch)); + } void SafeRemoveClipRange(angle_t startangle, angle_t endangle) { @@ -146,10 +152,14 @@ public: { return blocked; } - - angle_t PointToPseudoAngle(double x, double y); + + angle_t PointToPseudoAngle(double x, double y); + angle_t PointToPseudoPitch(double x, double y, double z); + angle_t PointToPseudoOrthoAngle(double x, double y); + angle_t PointToPseudoOrthoPitch(double x, double y, double z); bool CheckBox(const float *bspcoord); + bool CheckBoxOrthoPitch(const float *bspcoord); // Used to speed up angle calculations during clipping inline angle_t GetClipAngle(vertex_t *v) diff --git a/src/rendering/hwrenderer/scene/hw_drawinfo.cpp b/src/rendering/hwrenderer/scene/hw_drawinfo.cpp index 2275d9a11..50a81b2c4 100644 --- a/src/rendering/hwrenderer/scene/hw_drawinfo.cpp +++ b/src/rendering/hwrenderer/scene/hw_drawinfo.cpp @@ -131,12 +131,19 @@ HWDrawInfo *HWDrawInfo::StartDrawInfo(FLevelLocals *lev, HWDrawInfo *parent, FRe //========================================================================== static Clipper staticClipper; // Since all scenes are processed sequentially we only need one clipper. +static Clipper staticVClipper; // Another clipper to clip vertically (used if (VPSF_ALLOWOUTOFBOUNDS & camera->viewpos->Flags)). +static Clipper staticRClipper; // Another clipper for radar (doesn't actually clip. Changes SSECMF_DRAWN setting). static HWDrawInfo * gl_drawinfo; // This is a linked list of all active DrawInfos and needed to free the memory arena after the last one goes out of scope. void HWDrawInfo::StartScene(FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms) { staticClipper.Clear(); + staticVClipper.Clear(); + staticRClipper.Clear(); mClipper = &staticClipper; + vClipper = &staticVClipper; + rClipper = &staticRClipper; + rClipper->amRadar = true; Viewpoint = parentvp; lightmode = getRealLightmode(Level, true); @@ -169,6 +176,8 @@ void HWDrawInfo::StartScene(FRenderViewpoint &parentvp, HWViewpointUniforms *uni VPUniforms.mLightBlendMode = (level.info ? (int)level.info->lightblendmode : 0); } mClipper->SetViewpoint(Viewpoint); + vClipper->SetViewpoint(Viewpoint); + rClipper->SetViewpoint(Viewpoint); ClearBuffers(); @@ -257,7 +266,9 @@ void HWDrawInfo::ClearBuffers() void HWDrawInfo::UpdateCurrentMapSection() { - const int mapsection = Level->PointInRenderSubsector(Viewpoint.Pos)->mapsection; + int mapsection = Level->PointInRenderSubsector(Viewpoint.Pos)->mapsection; + if (Viewpoint.IsAllowedOoB()) + mapsection = Level->PointInRenderSubsector(Viewpoint.camera->Pos())->mapsection; CurrentMapSections.Set(mapsection); } @@ -270,9 +281,11 @@ void HWDrawInfo::UpdateCurrentMapSection() void HWDrawInfo::SetViewArea() { - auto &vp = Viewpoint; + auto &vp = Viewpoint; // The render_sector is better suited to represent the current position in GL vp.sector = Level->PointInRenderSubsector(vp.Pos)->render_sector; + if (Viewpoint.IsAllowedOoB()) + vp.sector = Level->PointInRenderSubsector(vp.camera->Pos())->render_sector; // Get the heightsec state from the render sector, not the current one! if (vp.sector->GetHeightSec()) @@ -351,16 +364,14 @@ int HWDrawInfo::SetFullbrightFlags(player_t *player) angle_t HWDrawInfo::FrustumAngle() { - float tilt = fabs(Viewpoint.HWAngles.Pitch.Degrees()); + // If pitch is larger than this you can look all around at an FOV of 90 degrees + if (fabs(Viewpoint.HWAngles.Pitch.Degrees()) > 89.0) return 0xffffffff; - // If the pitch is larger than this you can look all around at a FOV of 90° - if (tilt > 46.0f) return 0xffffffff; + double xratio = r_viewwindow.FocalTangent / Viewpoint.PitchCos; + double floatangle = 0.035 + atan ( xratio ) * 48.0 / AspectMultiplier(r_viewwindow.WidescreenRatio); // this is radians + angle_t a1 = DAngle::fromRad(floatangle).BAMs(); - // ok, this is a gross hack that barely works... - // but at least it doesn't overestimate too much... - double floatangle = 2.0 + (45.0 + ((tilt / 1.9)))*Viewpoint.FieldOfView.Degrees() * 48.0 / AspectMultiplier(r_viewwindow.WidescreenRatio) / 90.0; - angle_t a1 = DAngle::fromDeg(floatangle).BAMs(); - if (a1 >= ANGLE_180) return 0xffffffff; + if (a1 >= ANGLE_90) return 0xffffffff; return a1; } @@ -438,8 +449,12 @@ HWDecal *HWDrawInfo::AddDecal(bool onmirror) void HWDrawInfo::CreateScene(bool drawpsprites) { const auto &vp = Viewpoint; - angle_t a1 = FrustumAngle(); + angle_t a1 = FrustumAngle(); // horizontally clip the back of the viewport mClipper->SafeAddClipRangeRealAngles(vp.Angles.Yaw.BAMs() + a1, vp.Angles.Yaw.BAMs() - a1); + Viewpoint.FrustAngle = a1; + double a2 = 20.0 + 0.5*Viewpoint.FieldOfView.Degrees(); // FrustumPitch for vertical clipping + if (a2 > 179.0) a2 = 179.0; + vClipper->SafeAddClipRangeDegPitches(vp.HWAngles.Pitch.Degrees() - a2, vp.HWAngles.Pitch.Degrees() + a2); // clip the suplex range // reset the portal manager portalState.StartFrame(); @@ -648,6 +663,79 @@ void HWDrawInfo::DrawCorona(FRenderState& state, ACorona* corona, double dist) #endif } +//========================================================================== +// +// TraceCallbackForDitherTransparency +// Toggles dither flag on anything that occludes the actor's +// position from viewpoint. +// +//========================================================================== + +static ETraceStatus TraceCallbackForDitherTransparency(FTraceResults& res, void* userdata) +{ + int* count = (int*)userdata; + double bf, bc; + (*count)++; + switch(res.HitType) + { + case TRACE_HitWall: + if (!(res.Line->sidedef[res.Side]->Flags & WALLF_DITHERTRANS)) + { + bf = res.Line->sidedef[res.Side]->sector->floorplane.ZatPoint(res.HitPos.XY()); + bc = res.Line->sidedef[res.Side]->sector->ceilingplane.ZatPoint(res.HitPos.XY()); + if ((res.HitPos.Z <= bc) && (res.HitPos.Z >= bf)) res.Line->sidedef[res.Side]->Flags |= WALLF_DITHERTRANS; + } + break; + case TRACE_HitFloor: + res.Sector->floorplane.dithertransflag = true; + break; + case TRACE_HitCeiling: + res.Sector->ceilingplane.dithertransflag = true; + break; + case TRACE_HitActor: + default: + break; + } + + return TRACE_ContinueOutOfBounds; +} + + +void HWDrawInfo::SetDitherTransFlags(AActor* actor) +{ + if (actor && actor->Sector) + { + FTraceResults results; + double horix = Viewpoint.Sin * actor->radius; + double horiy = Viewpoint.Cos * actor->radius; + DVector3 actorpos = actor->Pos(); + DVector3 vvec = actorpos - Viewpoint.Pos; + if (Viewpoint.IsOrtho()) + { + vvec += Viewpoint.camera->Pos() - actorpos; + vvec *= 5.0; // Should be 4.0? (since zNear is behind screen by 3*dist in VREyeInfo::GetProjection()) + } + double distance = vvec.Length() - actor->radius; + DVector3 campos = actorpos - vvec; + sector_t* startsec; + int count = 0; + + vvec = vvec.Unit(); + campos.X -= horix; campos.Y += horiy; campos.Z += actor->Height * 0.25; + for (int iter = 0; iter < 3; iter++) + { + startsec = Level->PointInRenderSubsector(campos)->sector; + Trace(campos, startsec, vvec, distance, + 0, 0, actor, results, 0, TraceCallbackForDitherTransparency, &count); + campos.Z += actor->Height * 0.5; + Trace(campos, startsec, vvec, distance, + 0, 0, actor, results, 0, TraceCallbackForDitherTransparency, &count); + campos.Z -= actor->Height * 0.5; + campos.X += horix; campos.Y -= horiy; + } + } +} + static ETraceStatus CheckForViewpointActor(FTraceResults& res, void* userdata) { FRenderViewpoint* data = (FRenderViewpoint*)userdata; @@ -803,13 +891,17 @@ void HWDrawInfo::DrawScene(int drawmode) { static int recursion = 0; static int ssao_portals_available = 0; - const auto& vp = Viewpoint; + auto& vp = Viewpoint; bool applySSAO = false; if (drawmode == DM_MAINVIEW) { ssao_portals_available = gl_ssao_portals; applySSAO = true; + if (r_dithertransparency && vp.IsAllowedOoB()) + { + vp.camera->tracer ? SetDitherTransFlags(vp.camera->tracer) : SetDitherTransFlags(players[consoleplayer].mo); + } } else if (drawmode == DM_OFFSCREEN) { @@ -864,6 +956,8 @@ void HWDrawInfo::ProcessScene(bool toscreen) portalState.BeginScene(); int mapsection = Level->PointInRenderSubsector(Viewpoint.Pos)->mapsection; + if (Viewpoint.IsAllowedOoB()) + mapsection = Level->PointInRenderSubsector(Viewpoint.camera->Pos())->mapsection; CurrentMapSections.Set(mapsection); DrawScene(toscreen ? DM_MAINVIEW : DM_OFFSCREEN); diff --git a/src/rendering/hwrenderer/scene/hw_drawinfo.h b/src/rendering/hwrenderer/scene/hw_drawinfo.h index 0d298d9ea..ce848e297 100644 --- a/src/rendering/hwrenderer/scene/hw_drawinfo.h +++ b/src/rendering/hwrenderer/scene/hw_drawinfo.h @@ -146,6 +146,8 @@ struct HWDrawInfo HWPortal *mCurrentPortal; //FRotator mAngles; Clipper *mClipper; + Clipper *vClipper; // Vertical clipper + Clipper *rClipper; // Radar clipper FRenderViewpoint Viewpoint; HWViewpointUniforms VPUniforms; // per-viewpoint uniform state TArray Portals; @@ -234,6 +236,7 @@ public: HWPortal * FindPortal(const void * src); void RenderBSPNode(void *node); + void RenderOrthoNoFog(); void RenderBSP(void *node, bool drawpsprites); static HWDrawInfo *StartDrawInfo(FLevelLocals *lev, HWDrawInfo *parent, FRenderViewpoint &parentvp, HWViewpointUniforms *uniforms); @@ -302,6 +305,8 @@ public: void DrawCoronas(FRenderState& state); void DrawCorona(FRenderState& state, ACorona* corona, double dist); + void SetDitherTransFlags(AActor* actor); + void ProcessLowerMinisegs(TArray &lowersegs); void AddSubsectorToPortal(FSectorPortalGroup *portal, subsector_t *sub); diff --git a/src/rendering/hwrenderer/scene/hw_flats.cpp b/src/rendering/hwrenderer/scene/hw_flats.cpp index 459fe6445..f423c73fc 100644 --- a/src/rendering/hwrenderer/scene/hw_flats.cpp +++ b/src/rendering/hwrenderer/scene/hw_flats.cpp @@ -320,7 +320,7 @@ void HWFlat::DrawFlat(HWDrawInfo *di, FRenderState &state, bool translucent) state.SetObjectColor(FlatColor | 0xff000000); state.SetAddColor(AddColor | 0xff000000); state.ApplyTextureManipulation(TextureFx); - + if (plane.plane.dithertransflag) state.SetEffect(EFF_DITHERTRANS); if (hacktype & SSRF_PLANEHACK) { @@ -372,6 +372,7 @@ void HWFlat::DrawFlat(HWDrawInfo *di, FRenderState &state, bool translucent) state.SetObjectColor(0xffffffff); state.SetAddColor(0); state.ApplyTextureManipulation(nullptr); + if (plane.plane.dithertransflag) state.SetEffect(EFF_NONE); } //========================================================================== @@ -408,6 +409,8 @@ inline void HWFlat::PutFlat(HWDrawInfo *di, bool fog) void HWFlat::Process(HWDrawInfo *di, sector_t * model, int whichplane, bool fog) { plane.GetFromSector(model, whichplane); + model->ceilingplane.dithertransflag = false; // Resetting this every frame + model->floorplane.dithertransflag = false; // Resetting this every frame if (whichplane != int(ceiling)) { // Flip the normal if the source plane has a different orientation than what we are about to render. @@ -506,7 +509,7 @@ void HWFlat::ProcessSector(HWDrawInfo *di, sector_t * frontsector, int which) uint8_t sink; uint8_t &srf = hacktype? sink : di->section_renderflags[di->Level->sections.SectionIndex(section)]; - const auto &vp = di->Viewpoint; + auto &vp = di->Viewpoint; // // @@ -515,7 +518,7 @@ void HWFlat::ProcessSector(HWDrawInfo *di, sector_t * frontsector, int which) // // // - if ((which & SSRF_RENDERFLOOR) && frontsector->floorplane.ZatPoint(vp.Pos) <= vp.Pos.Z && (!section || !(section->flags & FSection::DONTRENDERFLOOR))) + if (((which & SSRF_RENDERFLOOR) && frontsector->floorplane.ZatPoint(vp.Pos) <= vp.Pos.Z && (!section || !(section->flags & FSection::DONTRENDERFLOOR)))&& !(vp.IsOrtho() && (vp.PitchSin < 0.0))) { // process the original floor first. @@ -573,7 +576,7 @@ void HWFlat::ProcessSector(HWDrawInfo *di, sector_t * frontsector, int which) // // // - if ((which & SSRF_RENDERCEILING) && frontsector->ceilingplane.ZatPoint(vp.Pos) >= vp.Pos.Z && (!section || !(section->flags & FSection::DONTRENDERCEILING))) + if (((which & SSRF_RENDERCEILING) && frontsector->ceilingplane.ZatPoint(vp.Pos) >= vp.Pos.Z && (!section || !(section->flags & FSection::DONTRENDERCEILING))) && !(vp.IsOrtho() && (vp.PitchSin > 0.0))) { // process the original ceiling first. diff --git a/src/rendering/hwrenderer/scene/hw_portal.cpp b/src/rendering/hwrenderer/scene/hw_portal.cpp index a453827f3..b0ea3a8c9 100644 --- a/src/rendering/hwrenderer/scene/hw_portal.cpp +++ b/src/rendering/hwrenderer/scene/hw_portal.cpp @@ -563,7 +563,8 @@ bool HWMirrorPortal::Setup(HWDrawInfo *di, FRenderState &rstate, Clipper *clippe angle_t af = di->FrustumAngle(); if (af < ANGLE_180) clipper->SafeAddClipRangeRealAngles(vp.Angles.Yaw.BAMs() + af, vp.Angles.Yaw.BAMs() - af); - clipper->SafeAddClipRange(linedef->v1, linedef->v2); + if(!di->Viewpoint.IsAllowedOoB()) + clipper->SafeAddClipRange(linedef->v1, linedef->v2); return true; } diff --git a/src/rendering/hwrenderer/scene/hw_sky.cpp b/src/rendering/hwrenderer/scene/hw_sky.cpp index 45ff8c5e7..6d77a275d 100644 --- a/src/rendering/hwrenderer/scene/hw_sky.cpp +++ b/src/rendering/hwrenderer/scene/hw_sky.cpp @@ -130,6 +130,7 @@ void HWSkyInfo::init(HWDrawInfo *di, sector_t* sec, int skypos, int sky1, PalEnt void HWWall::SkyPlane(HWWallDispatcher *di, sector_t *sector, int plane, bool allowreflect) { int ptype = -1; + if (di->di && di->di->Viewpoint.IsAllowedOoB()) return; // Couldn't prevent sky portal occlusion. Skybox is bad in ortho too. FSectorPortal *sportal = sector->ValidatePortal(plane); if (sportal != nullptr && sportal->mFlags & PORTSF_INSKYBOX) sportal = nullptr; // no recursions, delete it here to simplify the following code diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index 3f595d177..ba4b67983 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -65,6 +65,7 @@ extern TArray SpriteFrames; extern uint32_t r_renderercaps; const float LARGE_VALUE = 1e19f; +const float MY_SQRT2 = 1.41421356237309504880; // sqrt(2) EXTERN_CVAR(Bool, r_debug_disable_vis_filter) EXTERN_CVAR(Float, transsouls) @@ -494,7 +495,7 @@ bool HWSprite::CalculateVertices(HWDrawInfo* di, FVector3* v, DVector3* vp) } else // traditional "Y" billboard mode { - if (doRoll || !offset.isZero()) + if (doRoll || !offset.isZero() || (actor && (actor->renderflags2 & RF2_ISOMETRICSPRITES))) { mat.MakeIdentity(); @@ -516,6 +517,16 @@ bool HWSprite::CalculateVertices(HWDrawInfo* di, FVector3* v, DVector3* vp) mat.Translate(-center.X, -center.Z, -center.Y); } + if (actor && (actor->renderflags2 & RF2_ISOMETRICSPRITES) && di->Viewpoint.IsOrtho()) + { + float angleRad = (FAngle::fromDeg(270.) - HWAngles.Yaw).Radians(); + mat.Translate(center.X, center.Z, center.Y); + mat.Translate(0.0, z2 - center.Z, 0.0); + mat.Rotate(-sin(angleRad), 0, cos(angleRad), -actor->isotheta); + mat.Translate(0.0, center.Z - z2, 0.0); + mat.Translate(-center.X, -center.Z, -center.Y); + } + v[0] = mat * FVector3(x1, z1, y1); v[1] = mat * FVector3(x2, z1, y2); v[2] = mat * FVector3(x1, z2, y1); @@ -911,6 +922,7 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t { bool mirror = false; DAngle ang = (thingpos - vp.Pos).Angle(); + if (di->Viewpoint.IsOrtho()) ang = vp.Angles.Yaw; FTextureID patch; // [ZZ] add direct picnum override if (isPicnumOverride) @@ -1004,7 +1016,25 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t if (thing->renderflags & RF_SPRITEFLIP) // [SP] Flip back thing->renderflags ^= RF_XFLIP; - r.Scale(sprscale.X, isSpriteShadow ? sprscale.Y * 0.15 : sprscale.Y); + // If sprite is isometric, do both vertical scaling and partial rotation to face the camera to compensate for Y-billboarding. + // Using just rotation (about z=0) might cause tall+slender (high aspect ratio) sprites to clip out of collision box + // at the top and clip into whatever is behind them from the viewpoint's perspective. - [DVR] + thing->isoscaleY = 1.0; + thing->isotheta = vp.HWAngles.Pitch.Degrees(); + if (thing->renderflags2 & RF2_ISOMETRICSPRITES) + { + float floordist = thing->radius * vp.floordistfact; + floordist -= 0.5 * r.width * vp.cotfloor; + float sineisotheta = floordist / r.height; + double scl = g_sqrt( 1.0 + sineisotheta * sineisotheta - 2.0 * vp.PitchSin * sineisotheta ); + if ((thing->radius > 0.0) && (scl > fabs(vp.PitchCos))) + { + thing->isoscaleY = scl / ( fabs(vp.PitchCos) > 0.01 ? fabs(vp.PitchCos) : 0.01 ); + thing->isotheta = 180.0 * asin( sineisotheta / thing->isoscaleY ) / M_PI; + } + } + + r.Scale(sprscale.X, isSpriteShadow ? sprscale.Y * 0.15 * thing->isoscaleY : sprscale.Y * thing->isoscaleY); if (thing->renderflags & (RF_ROLLSPRITE|RF_FLATSPRITE)) { @@ -1018,7 +1048,7 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t z1 = z - r.top; z2 = z1 - r.height; - float spriteheight = sprscale.Y * r.height; + float spriteheight = sprscale.Y * r.height * thing->isoscaleY; // Tests show that this doesn't look good for many decorations and corpses if (spriteheight > 0 && gl_spriteclip > 0 && (thing->renderflags & RF_SPRITETYPEMASK) == RF_FACESPRITE) @@ -1037,6 +1067,13 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t x2 = x - viewvecY*rightfac; y1 = y + viewvecX*leftfac; y2 = y + viewvecX*rightfac; + if (thing->renderflags2 & RF2_ISOMETRICSPRITES) // If sprites are drawn from an isometric perspective + { + x1 -= viewvecX * thing->radius * MY_SQRT2; + x2 -= viewvecX * thing->radius * MY_SQRT2; + y1 -= viewvecY * thing->radius * MY_SQRT2; + y2 -= viewvecY * thing->radius * MY_SQRT2; + } break; } case RF_FLATSPRITE: @@ -1077,6 +1114,7 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t } depth = (float)((x - vp.Pos.X) * vp.TanCos + (y - vp.Pos.Y) * vp.TanSin); + if(thing->renderflags2 & RF2_ISOMETRICSPRITES) depth = depth * vp.PitchCos - vp.PitchSin * z2; // Helps with stacking actors with small xy offsets if (isSpriteShadow) depth += 1.f/65536.f; // always sort shadows behind the sprite. // light calculation diff --git a/src/rendering/hwrenderer/scene/hw_walls.cpp b/src/rendering/hwrenderer/scene/hw_walls.cpp index 75aa7a849..5c5e357d5 100644 --- a/src/rendering/hwrenderer/scene/hw_walls.cpp +++ b/src/rendering/hwrenderer/scene/hw_walls.cpp @@ -83,10 +83,16 @@ void SetSplitPlanes(FRenderState& state, const secplane_t& top, const secplane_t void HWWall::RenderWall(FRenderState &state, int textured) { + if (seg->sidedef->Flags & WALLF_DITHERTRANS) state.SetEffect(EFF_DITHERTRANS); assert(vertcount > 0); state.SetLightIndex(dynlightindex); state.Draw(DT_TriangleFan, vertindex, vertcount); vertexcount += vertcount; + if (seg->sidedef->Flags & WALLF_DITHERTRANS) + { + state.SetEffect(EFF_NONE); + seg->sidedef->Flags &= ~WALLF_DITHERTRANS; // reset this every frame + } } //========================================================================== @@ -2175,6 +2181,8 @@ void HWWall::Process(HWWallDispatcher *di, seg_t *seg, sector_t * frontsector, s } bool isportal = seg->linedef->isVisualPortal() && seg->sidedef == seg->linedef->sidedef[0]; + // Don't render portal insides if in orthographic mode + if (di->di) isportal &= !(di->di->Viewpoint.IsOrtho()); //return; // [GZ] 3D middle textures are necessarily two-sided, even if they lack the explicit two-sided flag diff --git a/src/rendering/r_utility.cpp b/src/rendering/r_utility.cpp index 2a5d95586..3e40c3a0c 100644 --- a/src/rendering/r_utility.cpp +++ b/src/rendering/r_utility.cpp @@ -66,7 +66,9 @@ #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 @@ -103,6 +105,8 @@ 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; @@ -155,9 +159,14 @@ FRenderViewpoint::FRenderViewpoint() 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; @@ -552,39 +561,42 @@ void R_InterpolateView(FRenderViewpoint& viewPoint, const player_t* const player // Due to interpolation this is not necessarily the same as the sector the camera is in. viewPoint.sector = viewLvl->PointInRenderSubsector(viewPoint.Pos)->sector; - bool moved = false; - while (!viewPoint.sector->PortalBlocksMovement(sector_t::ceiling)) + if (!viewPoint.IsAllowedOoB() || !V_IsHardwareRenderer()) { - if (viewPoint.Pos.Z > viewPoint.sector->GetPortalPlaneZ(sector_t::ceiling)) + bool moved = false; + while (!viewPoint.sector->PortalBlocksMovement(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)) + if (viewPoint.Pos.Z > viewPoint.sector->GetPortalPlaneZ(sector_t::ceiling)) { - const DVector2 offset = viewPoint.sector->GetPortalDisplacement(sector_t::floor); + const DVector2 offset = viewPoint.sector->GetPortalDisplacement(sector_t::ceiling); viewPoint.Pos += offset; viewPoint.ActorPos += offset; - viewPoint.sector = viewPoint.sector->GetPortal(sector_t::floor)->mDestination; + 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)) @@ -680,10 +692,46 @@ void FRenderViewpoint::SetViewAngle(const FViewWindow& viewWindow) 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(); // [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)); } //========================================================================== @@ -1017,14 +1065,15 @@ void R_SetupFrame(FRenderViewpoint& viewPoint, const FViewWindow& viewWindow, AA viewPoint.SetViewAngle(viewWindow); // Keep the view within the sector's floor and ceiling - if (viewPoint.sector->PortalBlocksMovement(sector_t::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)) + 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) diff --git a/src/rendering/r_utility.h b/src/rendering/r_utility.h index 3013961eb..ca6d84b8c 100644 --- a/src/rendering/r_utility.h +++ b/src/rendering/r_utility.h @@ -33,10 +33,16 @@ struct FRenderViewpoint double Sin; // sin(Angles.Yaw) double TanCos; // FocalTangent * cos(Angles.Yaw) double TanSin; // FocalTangent * sin(Angles.Yaw) + double PitchCos; // cos(Angles.Pitch) + double PitchSin; // sin(Angles.Pitch) + double floordistfact; // used for isometric sprites Y-billboarding compensation in hw_sprites.cpp + double cotfloor; // used for isometric sprites Y-billboarding compensation in hw_sprites.cpp + angle_t FrustAngle; // FrustumAngle() result AActor *camera; // camera actor sector_t *sector; // [RH] keep track of sector viewing from DAngle FieldOfView; // current field of view + double ScreenProj; // Screen projection factor for orthographic projection double TicFrac; // fraction of tic for interpolation uint32_t FrameTime; // current frame's time in tics. @@ -45,6 +51,8 @@ struct FRenderViewpoint bool showviewer; // show the camera actor? bool bForceNoViewer; // Never show the camera Actor. void SetViewAngle(const FViewWindow& viewWindow); + bool IsAllowedOoB(); // Checks if camera actor exists, has viewpos, and viewpos has VPSF_ALLOWOUTOFBOUNDS flag set + bool IsOrtho(); // Checks if camera actor exists, has viewpos, and viewpos has VPSF_ORTHOGRAPHIC flag set }; diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 570dd2a4e..3993cd745 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -385,6 +385,7 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(RF2, FLIPSPRITEOFFSETY, AActor, renderflags2), DEFINE_FLAG(RF2, CAMFOLLOWSPLAYER, AActor, renderflags2), DEFINE_FLAG(RF2, NOMIPMAP, AActor, renderflags2), + DEFINE_FLAG(RF2, ISOMETRICSPRITES, AActor, renderflags2), // Bounce flags DEFINE_FLAG2(BOUNCE_Walls, BOUNCEONWALLS, AActor, BounceFlags), diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index 32891e564..95f248d69 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -48,6 +48,23 @@ const int TEXF_Detailmap = 0x20000; const int TEXF_Glowmap = 0x40000; const int TEXF_ClampY = 0x80000; +//=========================================================================== +// +// RGB to HSV +// +//=========================================================================== + +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + //=========================================================================== // // Color to grayscale @@ -870,6 +887,27 @@ void main() frag.rgb = frag.rgb + uFogColor.rgb; } FragColor = frag; + +#ifdef DITHERTRANS + int index = (int(pixelpos.x) % 8) * 8 + int(pixelpos.y) % 8; + const float DITHER_THRESHOLDS[64] = + { + 1.0 / 65.0, 33.0 / 65.0, 9.0 / 65.0, 41.0 / 65.0, 3.0 / 65.0, 35.0 / 65.0, 11.0 / 65.0, 43.0 / 65.0, + 49.0 / 65.0, 17.0 / 65.0, 57.0 / 65.0, 25.0 / 65.0, 51.0 / 65.0, 19.0 / 65.0, 59.0 / 65.0, 27.0 / 65.0, + 13.0 / 65.0, 45.0 / 65.0, 5.0 / 65.0, 37.0 / 65.0, 15.0 / 65.0, 47.0 / 65.0, 7.0 / 65.0, 39.0 / 65.0, + 61.0 / 65.0, 29.0 / 65.0, 53.0 / 65.0, 21.0 / 65.0, 63.0 / 65.0, 31.0 / 65.0, 55.0 / 65.0, 23.0 / 65.0, + 4.0 / 65.0, 36.0 / 65.0, 12.0 / 65.0, 44.0 / 65.0, 2.0 / 65.0, 34.0 / 65.0, 10.0 / 65.0, 42.0 / 65.0, + 52.0 / 65.0, 20.0 / 65.0, 60.0 / 65.0, 28.0 / 65.0, 50.0 / 65.0, 18.0 / 65.0, 58.0 / 65.0, 26.0 / 65.0, + 16.0 / 65.0, 48.0 / 65.0, 8.0 / 65.0, 40.0 / 65.0, 14.0 / 65.0, 46.0 / 65.0, 6.0 / 65.0, 38.0 / 65.0, + 64.0 / 65.0, 32.0 / 65.0, 56.0 / 65.0, 24.0 / 65.0, 62.0 / 65.0, 30.0 / 65.0, 54.0 / 65.0, 22.0 /65.0 + }; + + vec3 fragHSV = rgb2hsv(FragColor.rgb); + float brightness = clamp(1.5*fragHSV.z, 0.1, 1.0); + if (DITHER_THRESHOLDS[index] < brightness) discard; + else FragColor *= 0.5; +#endif + #ifdef GBUFFER_PASS FragFog = vec4(AmbientOcclusionColor(), 1.0); FragNormal = vec4(vEyeNormal.xyz * 0.5 + 0.5, 1.0); diff --git a/wadsrc/static/shaders_gles/glsl/main.fp b/wadsrc/static/shaders_gles/glsl/main.fp index 99e64ca76..90429f8e0 100644 --- a/wadsrc/static/shaders_gles/glsl/main.fp +++ b/wadsrc/static/shaders_gles/glsl/main.fp @@ -493,6 +493,11 @@ void main() if (frag.a <= uAlphaThreshold) discard; #endif +#ifdef DITHERTRANS + int index = (int(pixelpos.x) % 2) * 2 + int(pixelpos.y) % 2; + if (index != 2) discard; +#endif + #if (DEF_FOG_2D == 0) // check for special 2D 'fog' mode. { float fogdist = 0.0; diff --git a/wadsrc/static/zscript/actors/shared/camera.zs b/wadsrc/static/zscript/actors/shared/camera.zs index d7826c051..463ad6178 100644 --- a/wadsrc/static/zscript/actors/shared/camera.zs +++ b/wadsrc/static/zscript/actors/shared/camera.zs @@ -128,3 +128,87 @@ class AimingCamera : SecurityCamera } } + +class SpectatorCamera : Actor +{ + + bool chasingtracer; + double lagdistance; // camera gives chase (lazy follow) if tracer gets > lagdistance away from camera.pos + int chasemode; // 0: chase until tracer centered, 1: same but only when tracer is moving, 2: stop chase if tracer within lagdistance + property lagdistance : lagdistance; + property chasingtracer : chasingtracer; + property chasemode : chasemode; + + default + { + +NOBLOCKMAP + +NOGRAVITY + +NOINTERACTION + RenderStyle "None"; + CameraHeight 0; + SpectatorCamera.chasingtracer false; + SpectatorCamera.lagdistance 0.0; + SpectatorCamera.chasemode 0; + } + + void Init(double dist, double yaw, double inpitch, int inflags) + { + + double zshift = 0.0; + if(tracer != NULL) + { + if(player != NULL) zshift = -0.25*tracer.height; + else zshift = 0.5*tracer.height; + } + else if (player != NULL && player.mo != NULL) zshift = -0.5*player.mo.height; + + SetViewPos((-dist*Cos(yaw)*Cos(inpitch), -dist*Sin(yaw)*Cos(inpitch), dist*Sin(inpitch)+zshift), inflags); + LookAtSelf(inpitch); + } + + void LookAtSelf(double inpitch) + { + if(ViewPos.Offset.length() > 0.) + { + Vector3 negviewpos = (-1.0) * ViewPos.Offset; + angle = negviewpos.Angle(); + pitch = inpitch; + } + } + + override void Tick() + { + if(tracer != NULL) + { + Vector3 distvec = tracer.pos - pos; + double dist = distvec.length(); + if((dist <= 4 && chasingtracer) || lagdistance <= 0.0) // Keep tracer centered on screen + { + SetOrigin(tracer.pos, true); + chasingtracer = false; + } + else // Lazy follow tracer + { + if(dist >= 2*lagdistance) + { + SetOrigin(tracer.pos, true); + chasingtracer = false; + } + else if(dist > lagdistance && !chasingtracer) chasingtracer = true; + + if(chasingtracer) + { + speed = tracer.vel.xy.length()/dist; + if((speed == 0.0) && (chasemode == 0)) speed = tracer.speed/dist; + SetOrigin(pos + 2*distvec*speed, true); + if(chasemode > 1) chasingtracer = false; + } + } + } + else if(player != NULL && player.mo != NULL) + { + cameraFOV = player.FOV; + SetOrigin(player.mo.pos, true); + } + } +} diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index ad77dc8b2..b4f2d85b7 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -421,8 +421,10 @@ enum EActivationFlags // [MC] Flags for SetViewPos. enum EViewPosFlags { - VPSF_ABSOLUTEOFFSET = 1 << 1, // Don't include angles. - VPSF_ABSOLUTEPOS = 1 << 2, // Use absolute position. + VPSF_ABSOLUTEOFFSET = 1 << 1, // Don't include angles. + VPSF_ABSOLUTEPOS = 1 << 2, // Use absolute position. + VPSF_ALLOWOUTOFBOUNDS = 1 << 3, // Allow viewpoint to go out of bounds (hardware renderer only). + VPSF_ORTHOGRAPHIC = 1 << 4, // Use orthographic projection (hardware renderer only). }; // Flags for A_TakeInventory and A_TakeFromTarget @@ -1501,4 +1503,4 @@ enum EModelFlags MDL_MODELSAREATTACHMENTS = 1<<12, // any model index after 0 is treated as an attachment, and therefore will use the bone results of index 0 MDL_CORRECTPIXELSTRETCH = 1<<13, // ensure model does not distort with pixel stretch when pitch/roll is applied MDL_FORCECULLBACKFACES = 1<<14, -}; \ No newline at end of file +};