From 4e8d59951beb7b2b320da06150aa91af5cedea0a Mon Sep 17 00:00:00 2001 From: Major Cooke Date: Sun, 5 Dec 2021 13:51:47 -0600 Subject: [PATCH] Added `A_SetViewPos(Vector3 Offset, int Flags = -1)` - Offset: The offset from the actor's view to move the camera about. - Flags: (Default is -1, which means don't change flags) - VPSF_ABSOLUTEOFFSET: Don't include actor angles in calculation. - VPSF_ABSOLUTEPOS: Position is absolute, and disables all transformations. Modders are responsible for being portal aware! Notes: - `ViewPos` in Actor will be `null` until A_SetViewPos is called for the first time. **Issues:** - Hiding sprite while in portal incomplete. --- src/playsim/actor.h | 60 +++++++- src/playsim/p_local.h | 5 + src/playsim/p_map.cpp | 56 +++++++ src/playsim/p_mobj.cpp | 7 + src/rendering/hwrenderer/scene/hw_sprites.cpp | 6 + src/rendering/r_utility.cpp | 137 ++++++++++++++---- src/rendering/r_utility.h | 2 +- src/scripting/vmthunks_actors.cpp | 26 +++- wadsrc/static/zscript/actors/actor.zs | 8 + wadsrc/static/zscript/constants.zs | 7 + 10 files changed, 284 insertions(+), 30 deletions(-) diff --git a/src/playsim/actor.h b/src/playsim/actor.h index e83c0966d..2c4855cb4 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -654,6 +654,63 @@ struct FDropItem int Amount; }; +enum EViewPosFlags // [MC] Flags for SetViewPos. +{ + VPSF_ABSOLUTEOFFSET = 1 << 1, // Don't include angles. + VPSF_ABSOLUTEPOS = 1 << 2, // Use absolute position. +}; + +class FViewPosition : public DObject +{ + DECLARE_CLASS(FViewPosition, DObject); +public: + // Variables + // Exposed to ZScript + DVector3 Offset; + int Flags; + + // Engine only. + // Used to do a backwards trace to disable rendering over portals. + bool isUsed; + DVector3 PlrPos, + CamPos; + double Distance; + sector_t *CamSec; + + + // Functions + FViewPosition() + { + Offset = PlrPos = CamPos = { 0,0,0 }; + isUsed = false; + Distance = 0.0; + CamSec = nullptr; + } + + void Set(DVector3 &off, int f = -1) + { + Offset = off; + + if (f > -1) + Flags = f; + } + + bool isZero() + { + return Offset.isZero(); + } + + void ResetTraceInfo() + { + isUsed = false; + PlrPos = CamPos = {0,0,0}; + Distance = 0.; + CamSec = nullptr; + } + + bool TraceBack(AActor *Owner); +}; + const double MinVel = EQUAL_EPSILON; // Map Object definition. @@ -974,7 +1031,8 @@ public: DAngle SpriteAngle; DAngle SpriteRotation; DRotator Angles; - DRotator ViewAngles; // Offsets for cameras + DRotator ViewAngles; // Angle offsets for cameras + FViewPosition *ViewPos; // Position offsets for cameras DVector2 Scale; // Scaling values; 1 is normal size double Alpha; // Since P_CheckSight makes an alpha check this can't be a float. It has to be a double. diff --git a/src/playsim/p_local.h b/src/playsim/p_local.h index 8b0012d68..375c1c039 100644 --- a/src/playsim/p_local.h +++ b/src/playsim/p_local.h @@ -50,6 +50,7 @@ struct secplane_t; struct FCheckPosition; struct FTranslatedLineTarget; struct FLinePortal; +class FViewPosition; #include @@ -392,6 +393,10 @@ void P_PlaySpawnSound(AActor *missile, AActor *spawner); // [RH] Position the chasecam void P_AimCamera (AActor *t1, DVector3 &, DAngle &, sector_t *&sec, bool &unlinked); +// [MC] Aiming for ViewPos +void P_AdjustViewPos(AActor *t1, DVector3 orig, DVector3 &, sector_t *&sec, bool &unlinked, FViewPosition *VP); + + // [RH] Means of death enum { diff --git a/src/playsim/p_map.cpp b/src/playsim/p_map.cpp index 0cc2a524f..2b443d314 100644 --- a/src/playsim/p_map.cpp +++ b/src/playsim/p_map.cpp @@ -5423,6 +5423,62 @@ void P_AimCamera(AActor *t1, DVector3 &campos, DAngle &camangle, sector_t *&Came camangle = trace.SrcAngleFromTarget - 180.; } +// [MC] Used for ViewPos. Uses code borrowed from P_AimCamera. +void P_AdjustViewPos(AActor *t1, DVector3 orig, DVector3 &campos, sector_t *&CameraSector, bool &unlinked, FViewPosition *VP) +{ + FTraceResults trace; + const DVector3 vvec = campos - orig; + const double distance = vvec.Length(); + + // Trace handles all of the portal crossing, which is why there is no usage of Vec#Offset(Z). + if (Trace(orig, t1->Sector, vvec.Unit(), distance, 0, 0, t1, trace) && + trace.Distance > 5) + { + // Position camera slightly in front of hit thing + campos = orig + vvec.Unit() * (trace.Distance - 5); + } + else + { + campos = trace.HitPos - trace.HitVector * 1 / 256.; + } +// DVector3 cpos = campos; + CameraSector = trace.Sector; + unlinked = trace.unlinked; + + // Save the info for the renderers. Needed to disable sprites across portals. + VP->Distance = distance; + VP->isUsed = true; +} + +struct EViewPosFinder +{ + AActor *Owner; +}; + +static ETraceStatus TraceToOwner(FTraceResults &res, void *userdata) +{ + // Try to guarantee hitting the owner if possible. + if (res.HitType != TRACE_HitActor) + return TRACE_Skip; + + EViewPosFinder *data = (EViewPosFinder *)userdata; + ETraceStatus ret = (res.Actor == data->Owner) ? TRACE_Stop : TRACE_Skip; + return ret; +} + +bool FViewPosition::TraceBack(AActor *Owner) +{ + if (!isUsed || !Owner) + return false; + + DVector3 vvec = (CamPos - PlrPos).Unit() * -1.0; + FTraceResults res; + EViewPosFinder TrcBack = {Owner}; + + bool ret = Trace(CamPos, CamSec, vvec, Distance, 0, 0, nullptr, res, TRACE_ReportPortals, TraceToOwner, &TrcBack); + + return ret; +} //========================================================================== // diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index d2bc9aba1..c8ce4a291 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -369,6 +369,7 @@ void AActor::Serialize(FSerializer &arc) A("spawnorder", SpawnOrder) A("friction", Friction) A("SpriteOffset", SpriteOffset) + ("viewpos", ViewPos) A("userlights", UserLights); SerializeTerrain(arc, "floorterrain", floorterrain, &def->floorterrain); @@ -4952,6 +4953,12 @@ void AActor::OnDestroy () UnlinkFromWorld (nullptr); flags |= MF_NOSECTOR|MF_NOBLOCKMAP; + if (ViewPos != nullptr) + { + ViewPos->Destroy(); + ViewPos = nullptr; + } + // Transform any playing sound into positioned, non-actor sounds. S_RelinkSound (this, NULL); diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index b6a9c204b..902fc6f1a 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -734,6 +734,12 @@ void HWSprite::Process(HWDrawInfo *di, AActor* thing, sector_t * sector, area_t DVector3 thingorigin = thing->Pos(); if (thruportal == 1) thingorigin += di->Level->Displacements.getOffset(thing->Sector->PortalGroup, sector->PortalGroup); if (fabs(thingorigin.X - vp.ActorPos.X) < 2 && fabs(thingorigin.Y - vp.ActorPos.Y) < 2) return; + + // Try to do an inverted trace to get to the camera's body. This way, we know exactly behind which line to disable. + // Currently doesn't work. + FViewPosition *vpos = camera->ViewPos; + if (vpos && vpos->TraceBack(camera)) + return; } // Thing is invisible if close to the camera. if (thing->renderflags & RF_MAYBEINVISIBLE) diff --git a/src/rendering/r_utility.cpp b/src/rendering/r_utility.cpp index 4dcb03bc9..b1ca77e9a 100644 --- a/src/rendering/r_utility.cpp +++ b/src/rendering/r_utility.cpp @@ -449,8 +449,8 @@ void R_InterpolateView (FRenderViewpoint &viewpoint, player_t *player, double Fr // 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. - // Needless to say, this doesn't work for chasecam mode. - if (!viewpoint.showviewer) + // Needless to say, this doesn't work for chasecam mode or viewpos. + if (!viewpoint.showviewer && !viewpoint.NoPortalPath) { double pathlen = 0; double zdiff = 0; @@ -789,35 +789,118 @@ void R_SetupFrame (FRenderViewpoint &viewpoint, FViewWindow &viewwindow, AActor iview->otic = nowtic; iview->Old = iview->New; } + //============================================================================================== + // Handles offsetting the camera with ChaseCam and/or viewpos. + { + AActor *mo = viewpoint.camera; + FViewPosition *VP = mo->ViewPos; + if (VP) VP->ResetTraceInfo(); + const DVector3 orig = { mo->Pos().XY(), mo->player ? mo->player->viewz : mo->Z() + mo->GetCameraHeight() }; + viewpoint.ActorPos = orig; + + bool DefaultDraw = true; - if (player != NULL && gamestate != GS_TITLELEVEL && - ((player->cheats & CF_CHASECAM) || (r_deathcamera && viewpoint.camera->health <= 0))) - { sector_t *oldsector = viewpoint.ViewLevel->PointInRenderSubsector(iview->Old.Pos)->sector; - // [RH] Use chasecam view - DVector3 campos; - DAngle camangle; - P_AimCamera (viewpoint.camera, campos, camangle, viewpoint.sector, unlinked); // fixme: This needs to translate the angle, too. - iview->New.Pos = campos; - iview->New.Angles.Yaw = camangle; - - viewpoint.showviewer = true; - // Interpolating this is a very complicated thing because nothing keeps track of the aim camera's movement, so whenever we detect a portal transition - // it's probably best to just reset the interpolation for this move. - // Note that this can still cause problems with unusually linked portals - if (viewpoint.sector->PortalGroup != oldsector->PortalGroup || (unlinked && ((iview->New.Pos.XY() - iview->Old.Pos.XY()).LengthSquared()) > 256*256)) + if (gamestate != GS_TITLELEVEL && + (player && ((player->cheats & CF_CHASECAM)) || (r_deathcamera && viewpoint.camera->health <= 0))) { - iview->otic = nowtic; - iview->Old = iview->New; - r_NoInterpolate = true; + // [RH] Use chasecam view + DefaultDraw = false; + DVector3 campos; + DAngle camangle; + P_AimCamera(viewpoint.camera, campos, camangle, viewpoint.sector, unlinked); // fixme: This needs to translate the angle, too. + iview->New.Pos = campos; + iview->New.Angles.Yaw = camangle; + + viewpoint.showviewer = true; + // Interpolating this is a very complicated thing because nothing keeps track of the aim camera's movement, so whenever we detect a portal transition + // it's probably best to just reset the interpolation for this move. + // Note that this can still cause problems with unusually linked portals + if (viewpoint.sector->PortalGroup != oldsector->PortalGroup || (unlinked && ((iview->New.Pos.XY() - iview->Old.Pos.XY()).LengthSquared()) > 256 * 256)) + { + iview->otic = nowtic; + iview->Old = iview->New; + r_NoInterpolate = true; + } + viewpoint.ActorPos = campos; + } + else if (VP) // No chase/death cam and player is alive, wants viewpos. + { + viewpoint.sector = viewpoint.ViewLevel->PointInRenderSubsector(iview->New.Pos.XY())->sector; + viewpoint.showviewer = false; + + // [MC] Ignores all portal portal transitions since it's meant to be absolute. + // Modders must handle performing offsetting with the appropriate functions to get it to work. + // Hint: Check P_AdjustViewPos. + if (VP->Flags & VPSF_ABSOLUTEPOS) + { + iview->New.Pos = VP->Offset; + } + else + { + DVector3 next = orig; + + if (VP->isZero()) + { + // Since viewpos isn't being used, it's safe to enable path interpolation + viewpoint.NoPortalPath = false; + } + else if (VP->Flags & VPSF_ABSOLUTEOFFSET) + { + // No relativity added from angles. + next += VP->Offset; + } + else + { + // [MC] Do NOT handle portals here! Trace must have the unportaled (absolute) position to + // get the correct angle and distance. Trace automatically handles portals by itself. + // Note: viewpos does not include view angles, and ViewZ/CameraHeight are applied before this. + + DAngle yaw = mo->Angles.Yaw; + DAngle pitch = mo->Angles.Pitch; + DAngle roll = mo->Angles.Roll; + DVector3 relx, rely, relz, Off = VP->Offset; + DMatrix3x3 rot = + DMatrix3x3(DVector3(0., 0., 1.), yaw.Cos(), yaw.Sin()) * + DMatrix3x3(DVector3(0., 1., 0.), pitch.Cos(), pitch.Sin()) * + DMatrix3x3(DVector3(1., 0., 0.), roll.Cos(), roll.Sin()); + relx = DVector3(1., 0., 0.)*rot; + rely = DVector3(0., 1., 0.)*rot; + relz = DVector3(0., 0., 1.)*rot; + next += relx * Off.X + rely * Off.Y + relz * Off.Z; + } + + if (next != orig) + { + // [MC] Disable interpolation if the camera view is crossing through a portal. Sometimes + // the player is made visible when crossing a portal and it's extremely jarring. + // Also, disable the portal interpolation pathing entirely when using the viewpos feature. + // Interpolation still happens with everything else though and seems to work fine. + DefaultDraw = false; + viewpoint.NoPortalPath = true; + P_AdjustViewPos(mo, orig, next, viewpoint.sector, unlinked, VP); + + VP->PlrPos = orig; + VP->CamPos = next; + VP->CamSec = viewpoint.sector; + + if (viewpoint.sector->PortalGroup != oldsector->PortalGroup || (unlinked && ((iview->New.Pos.XY() - iview->Old.Pos.XY()).LengthSquared()) > 256 * 256)) + { + iview->otic = nowtic; + iview->Old = iview->New; + r_NoInterpolate = true; + } + iview->New.Pos = next; + } + } + } + + if (DefaultDraw) + { + iview->New.Pos = orig; + viewpoint.sector = viewpoint.camera->Sector; + viewpoint.showviewer = viewpoint.NoPortalPath = false; } - viewpoint.ActorPos = campos; - } - else - { - viewpoint.ActorPos = iview->New.Pos = { viewpoint.camera->Pos().XY(), viewpoint.camera->player ? viewpoint.camera->player->viewz : viewpoint.camera->Z() + viewpoint.camera->GetCameraHeight() }; - viewpoint.sector = viewpoint.camera->Sector; - viewpoint.showviewer = false; } // [MC] Apply the view angles first, which is the offsets. If the absolute isn't desired, diff --git a/src/rendering/r_utility.h b/src/rendering/r_utility.h index b4ac93c9b..77254ebec 100644 --- a/src/rendering/r_utility.h +++ b/src/rendering/r_utility.h @@ -43,7 +43,7 @@ struct FRenderViewpoint int extralight; // extralight to be added to this viewpoint bool showviewer; // show the camera actor? - + bool NoPortalPath; // Disable portal interpolation path for actor viewpos. void SetViewAngle(const FViewWindow &viewwindow); diff --git a/src/scripting/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index 2f5c26e9d..d16f0d4ca 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -1770,7 +1770,6 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, isFrozen, isFrozen) ACTION_RETURN_BOOL(isFrozen(self)); } - //=========================================================================== // // PlayerPawn functions @@ -1796,7 +1795,31 @@ DEFINE_ACTION_FUNCTION_NATIVE(APlayerPawn, GetPrintableDisplayName, GetPrintable ACTION_RETURN_STRING(type->GetDisplayName()); } +static void SetViewPos(AActor *self, double x, double y, double z, int flags) +{ + if (!self->ViewPos) + { + self->ViewPos = Create(); + } + DVector3 pos = { x,y,z }; + self->ViewPos->Set(pos, flags); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetViewPos, SetViewPos) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_FLOAT(x); + PARAM_FLOAT(y); + PARAM_FLOAT(z); + PARAM_INT(flags); + SetViewPos(self, x, y, z, flags); + return 0; +} + +IMPLEMENT_CLASS(FViewPosition, false, false); +DEFINE_FIELD_X(ViewPosition, FViewPosition, Offset) +DEFINE_FIELD_X(ViewPosition, FViewPosition, Flags) DEFINE_FIELD(DThinker, Level) DEFINE_FIELD(AActor, snext) @@ -1975,6 +1998,7 @@ DEFINE_FIELD(AActor, friendlyseeblocks) DEFINE_FIELD(AActor, SpawnTime) DEFINE_FIELD(AActor, InventoryID) DEFINE_FIELD(AActor, ThruBits) +DEFINE_FIELD(AActor, ViewPos) DEFINE_FIELD_NAMED(AActor, ViewAngles.Yaw, viewangle) DEFINE_FIELD_NAMED(AActor, ViewAngles.Pitch, viewpitch) DEFINE_FIELD_NAMED(AActor, ViewAngles.Roll, viewroll) diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index 3124a7bd0..7c39fabc9 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -67,6 +67,12 @@ struct LinkContext voidptr render_list; } +class ViewPosition native +{ + native readonly Vector3 Offset; + native readonly int Flags; +} + class Actor : Thinker native { @@ -89,6 +95,7 @@ class Actor : Thinker native // for some comments on these fields, see their native representations in actor.h. native readonly Actor snext; // next in sector list. native PlayerInfo Player; + native readonly ViewPosition ViewPos; // Will be null until A_SetViewPos() is called for the first time. native readonly vector3 Pos; native vector3 Prev; native uint ThruBits; @@ -1157,6 +1164,7 @@ class Actor : Thinker native native void A_SetViewAngle(double angle = 0, int flags = 0, int ptr = AAPTR_DEFAULT); native void A_SetViewPitch(double pitch, int flags = 0, int ptr = AAPTR_DEFAULT); native void A_SetViewRoll(double roll, int flags = 0, int ptr = AAPTR_DEFAULT); + native void SetViewPos(Vector3 offset, int flags = -1); deprecated("2.3", "User variables are deprecated in ZScript. Actor variables are directly accessible") native void A_SetUserVar(name varname, int value); deprecated("2.3", "User variables are deprecated in ZScript. Actor variables are directly accessible") native void A_SetUserArray(name varname, int index, int value); deprecated("2.3", "User variables are deprecated in ZScript. Actor variables are directly accessible") native void A_SetUserVarFloat(name varname, double value); diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index a3cf30519..de7eab3d9 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -396,6 +396,13 @@ enum EActivationFlags }; +// [MC] Flags for SetViewPos. +enum EViewPosFlags +{ + VPSF_ABSOLUTEOFFSET = 1 << 1, // Don't include angles. + VPSF_ABSOLUTEPOS = 1 << 2, // Use absolute position. +}; + // Flags for A_TakeInventory and A_TakeFromTarget enum ETakeFlags {