diff --git a/src/p_local.h b/src/p_local.h index 35dbb8b22..7e11010c5 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -243,6 +243,7 @@ extern msecnode_t *sector_list; // phares 3/16/98 struct spechit_t { line_t *line; + fixedvec2 oldrefpos; fixedvec2 refpos; }; diff --git a/src/p_map.cpp b/src/p_map.cpp index 66b4d39e4..960c82f47 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -969,12 +969,14 @@ bool PIT_CheckLine(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::Chec { spec.line = ld; spec.refpos = cres.position; + spec.oldrefpos = tm.thing->PosRelative(ld); spechit.Push(spec); } if (ld->portalindex >= 0 && ld->portalindex != UINT_MAX) { spec.line = ld; spec.refpos = cres.position; + spec.oldrefpos = tm.thing->PosRelative(ld); portalhit.Push(spec); } @@ -1526,7 +1528,6 @@ bool P_CheckPosition(AActor *thing, fixed_t x, fixed_t y, FCheckPosition &tm, bo tm.touchmidtex = false; tm.abovemidtex = false; validcount++; - spechit.Clear(); if ((thing->flags & MF_NOCLIP) && !(thing->flags & MF_SKULLFLY)) return true; @@ -1604,6 +1605,8 @@ bool P_CheckPosition(AActor *thing, fixed_t x, fixed_t y, FCheckPosition &tm, bo // a pickup they cannot get, because their validcount will prevent them from // being considered for collision with the player. validcount++; + spechit.Clear(); + portalhit.Clear(); thing->BlockingMobj = NULL; thing->height = realheight; @@ -2132,22 +2135,125 @@ bool P_TryMove(AActor *thing, fixed_t x, fixed_t y, return false; } - // the move is ok, so link the thing into its new position - thing->UnlinkFromWorld(); - oldpos = thing->Pos(); - oldsector = thing->Sector; - thing->floorz = tm.floorz; - thing->ceilingz = tm.ceilingz; - thing->dropoffz = tm.dropoffz; // killough 11/98: keep track of dropoffs - thing->floorpic = tm.floorpic; - thing->floorterrain = tm.floorterrain; - thing->floorsector = tm.floorsector; - thing->ceilingpic = tm.ceilingpic; - thing->ceilingsector = tm.ceilingsector; - thing->SetXY(x, y); + // Check for crossed portals + bool portalcrossed = false; - thing->LinkToWorld(); + while (true) + { + fixed_t bestfrac = FIXED_MAX; + spechit_t *besthit = NULL; + // find the portal nearest to the crossing actor + for (auto &spec : portalhit) + { + line_t *ld = spec.line; + if (ld->frontsector->PortalGroup != thing->Sector->PortalGroup) continue; // must be in the same group to be considered valid. + + // see if the line was crossed + oldside = P_PointOnLineSide(spec.oldrefpos.x, spec.oldrefpos.y, ld); + side = P_PointOnLineSide(spec.refpos.x, spec.refpos.y, ld); + if (oldside == 0 && side == 1) + { + divline_t dl2 = { ld->v1->x, ld->v1->y, ld->dx, ld->dy }; + divline_t dl1 = { spec.oldrefpos.x, spec.oldrefpos.y, spec.refpos.x - spec.oldrefpos.x, spec.refpos.y - spec.oldrefpos.y }; + fixed_t frac = P_InterceptVector(&dl1, &dl2); + if (frac < bestfrac) + { + besthit = &spec; + bestfrac = frac; + } + } + } + + if (bestfrac < FIXED_MAX) + { + line_t *ld = besthit->line; + FLinePortal *port = ld->getPortal(); + if (port->mType == PORTT_LINKED) + { + thing->UnlinkFromWorld(); + thing->SetXY(tm.x + port->mXDisplacement, tm.y + port->mYDisplacement); + thing->PrevX += port->mXDisplacement; + thing->PrevY += port->mYDisplacement; + thing->LinkToWorld(); + P_FindFloorCeiling(thing); + portalcrossed = true; + } + else if (!portalcrossed) + { + line_t *out = port->mDestination; + fixedvec3 pos = { tm.x, tm.y, thing->Z() }; + fixedvec3 oldthingpos = thing->Pos(); + fixedvec2 thingpos = oldthingpos; + + // This gets a bit tricky because we want to catch all line specials the actor crosses on the other side, too. + // So we have to translate the old Actor xy and temporarily set this as the actor's current position for the P_CheckPosition call, + // so that the spechit array gets proper positions assigned that can be evaluated later. + P_TranslatePortalXY(ld, out, pos.x, pos.y); + P_TranslatePortalXY(ld, out, thingpos.x, thingpos.y); + P_TranslatePortalVXVY(ld, out, thing->velx, thing->vely); + P_TranslatePortalAngle(ld, out, thing->angle); + P_TranslatePortalZ(ld, out, pos.z); + thing->SetXYZ(thingpos.x, thingpos.y, pos.z); + if (!P_CheckPosition(thing, pos.x, pos.y)) + { + thing->SetXYZ(oldthingpos); + thing->flags6 &= ~MF6_INTRYMOVE; + return false; + } + thing->UnlinkFromWorld(); + thing->SetXYZ(pos); + thing->LinkToWorld(); + P_FindFloorCeiling(thing); + thing->ClearInterpolation(); + } + // if this is the current camera we need to store the point where the portal was crossed and the exit + // so that the renderer can properly calculate an interpolated position along the movement path. + if (thing == players[consoleplayer].camera) + { + divline_t dl1 = { besthit->oldrefpos.x,besthit-> oldrefpos.y, besthit->refpos.x - besthit->oldrefpos.x, besthit->refpos.y - besthit->oldrefpos.y }; + fixedvec3 hit = { dl1.x + FixedMul(dl1.dx, bestfrac), dl1.y + FixedMul(dl1.dy, bestfrac), 0 }; + line_t *out = port->mDestination; + + R_AddInterpolationPoint(hit); + if (port->mType == PORTT_LINKED) + { + hit.x += port->mXDisplacement; + hit.y += port->mYDisplacement; + } + else + { + P_TranslatePortalXY(ld, out, hit.x, hit.y); + P_TranslatePortalZ(ld, out, hit.z); + } + R_AddInterpolationPoint(hit); + } + if (port->mType == PORTT_LINKED) continue; + } + break; + } + + + + if (!portalcrossed) + { + // the move is ok, so link the thing into its new position + thing->UnlinkFromWorld(); + + oldpos = thing->Pos(); + oldsector = thing->Sector; + thing->floorz = tm.floorz; + thing->ceilingz = tm.ceilingz; + thing->dropoffz = tm.dropoffz; // killough 11/98: keep track of dropoffs + thing->floorpic = tm.floorpic; + thing->floorterrain = tm.floorterrain; + thing->floorsector = tm.floorsector; + thing->ceilingpic = tm.ceilingpic; + thing->ceilingsector = tm.ceilingsector; + thing->SetXY(x, y); + + thing->LinkToWorld(); + } if (thing->flags2 & MF2_FLOORCLIP) { @@ -2161,10 +2267,9 @@ bool P_TryMove(AActor *thing, fixed_t x, fixed_t y, while (spechit.Pop(spec)) { line_t *ld = spec.line; - fixedvec3 oldrelpos = PosRelative(oldpos, ld, oldsector); // see if the line was crossed side = P_PointOnLineSide(spec.refpos.x, spec.refpos.y, ld); - oldside = P_PointOnLineSide(oldrelpos.x, oldrelpos.y, ld); + oldside = P_PointOnLineSide(spec.oldrefpos.x, spec.oldrefpos.y, ld); if (side != oldside && ld->special && !(thing->flags6 & MF6_NOTRIGGER)) { if (thing->player && (thing->player->cheats & CF_PREDICTING)) diff --git a/src/p_tick.cpp b/src/p_tick.cpp index 29b2cd4ad..4020bcebe 100644 --- a/src/p_tick.cpp +++ b/src/p_tick.cpp @@ -112,6 +112,7 @@ void P_Ticker (void) S_ResumeSound (false); P_ResetSightCounters (false); + R_ClearInterpolationPath(); // Since things will be moving, it's okay to interpolate them in the renderer. r_NoInterpolate = false; diff --git a/src/r_utility.cpp b/src/r_utility.cpp index 628310fce..0239aaa5d 100644 --- a/src/r_utility.cpp +++ b/src/r_utility.cpp @@ -81,6 +81,7 @@ static TArray PastViewers; static FRandom pr_torchflicker ("TorchFlicker"); static FRandom pr_hom; static bool NoInterpolateView; +static TArray InterpolationPath; // PUBLIC DATA DEFINITIONS ------------------------------------------------- @@ -574,6 +575,7 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi // frac = tf; if (NoInterpolateView) { + InterpolationPath.Clear(); NoInterpolateView = false; iview->oviewx = iview->nviewx; iview->oviewy = iview->nviewy; @@ -583,10 +585,67 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi } int oldgroup = R_PointInSubsector(iview->oviewx, iview->oviewy)->sector->PortalGroup; int newgroup = R_PointInSubsector(iview->nviewx, iview->nviewy)->sector->PortalGroup; - fixedvec2 disp = Displacements.getOffset(oldgroup, newgroup); - viewx = iview->oviewx + FixedMul (frac, iview->nviewx - iview->oviewx - disp.x); - viewy = iview->oviewy + FixedMul (frac, iview->nviewy - iview->oviewy - disp.y); - viewz = iview->oviewz + FixedMul (frac, iview->nviewz - iview->oviewz); + + if ((iview->oviewx != iview->nviewx || iview->oviewy != iview->nviewy) && InterpolationPath.Size() > 0) + { + viewx = iview->nviewx; + viewy = iview->nviewy; + viewz = iview->nviewz; + + // 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 (!r_showviewer) + { + fixed_t pathlen = 0; + fixed_t zdiff = 0; + fixed_t totalzdiff = 0; + fixed_t oviewz = iview->oviewz; + fixed_t nviewz = iview->nviewz; + fixedvec3 oldpos = { iview->oviewx, iview->oviewy, 0 }; + fixedvec3 newpos = { iview->nviewx, iview->nviewy, 0 }; + InterpolationPath.Push(newpos); // add this to the array to simplify the loops below + + for (unsigned i = 0; i < InterpolationPath.Size(); i += 2) + { + fixedvec3 &start = i == 0 ? oldpos : InterpolationPath[i - 1]; + fixedvec3 &end = InterpolationPath[i]; + pathlen += xs_CRoundToInt(TVector2(end.x - start.x, end.y - start.y).Length()); + totalzdiff += start.z; + } + fixed_t interpolatedlen = FixedMul(frac, pathlen); + + for (unsigned i = 0; i < InterpolationPath.Size(); i += 2) + { + fixedvec3 &start = i == 0 ? oldpos : InterpolationPath[i - 1]; + fixedvec3 &end = InterpolationPath[i]; + fixed_t fraglen = xs_CRoundToInt(TVector2(end.x - start.x, end.y - start.y).Length()); + zdiff += start.z; + if (fraglen <= interpolatedlen) + { + interpolatedlen -= fraglen; + } + else + { + fixed_t fragfrac = FixedDiv(interpolatedlen, fraglen); + oviewz += zdiff; + nviewz -= totalzdiff - zdiff; + viewx = start.x + FixedMul(fragfrac, end.x - start.x); + viewy = start.y + FixedMul(fragfrac, end.y - start.y); + viewz = oviewz + FixedMul(frac, nviewz - oviewz); + } + } + InterpolationPath.Pop(); + } + } + else + { + fixedvec2 disp = Displacements.getOffset(oldgroup, newgroup); + viewx = iview->oviewx + FixedMul(frac, iview->nviewx - iview->oviewx - disp.x); + viewy = iview->oviewy + FixedMul(frac, iview->nviewy - iview->oviewy - disp.y); + viewz = iview->oviewz + FixedMul(frac, iview->nviewz - iview->oviewz); + } if (player != NULL && !(player->cheats & CF_INTERPVIEW) && player - players == consoleplayer && @@ -678,6 +737,7 @@ void R_InterpolateView (player_t *player, fixed_t frac, InterpolationViewer *ivi void R_ResetViewInterpolation () { + InterpolationPath.Clear(); NoInterpolateView = true; } @@ -718,6 +778,7 @@ static InterpolationViewer *FindPastViewer (AActor *actor) InterpolationViewer iview = { NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; iview.ViewActor = actor; iview.otic = -1; + InterpolationPath.Clear(); return &PastViewers[PastViewers.Push (iview)]; } @@ -729,6 +790,7 @@ static InterpolationViewer *FindPastViewer (AActor *actor) void R_FreePastViewers () { + InterpolationPath.Clear(); PastViewers.Clear (); } @@ -742,6 +804,7 @@ void R_FreePastViewers () void R_ClearPastViewer (AActor *actor) { + InterpolationPath.Clear(); for (unsigned int i = 0; i < PastViewers.Size(); ++i) { if (PastViewers[i].ViewActor == actor) @@ -781,6 +844,7 @@ void R_RebuildViewInterpolation(player_t *player) iview->oviewz = iview->nviewz; iview->oviewpitch = iview->nviewpitch; iview->oviewangle = iview->nviewangle; + InterpolationPath.Clear(); } //========================================================================== @@ -794,6 +858,29 @@ bool R_GetViewInterpolationStatus() return NoInterpolateView; } + +//========================================================================== +// +// R_ClearInterpolationPath +// +//========================================================================== + +void R_ClearInterpolationPath() +{ + InterpolationPath.Clear(); +} + +//========================================================================== +// +// R_AddInterpolationPoint +// +//========================================================================== + +void R_AddInterpolationPoint(const fixedvec3 &vec) +{ + InterpolationPath.Push(vec); +} + //========================================================================== // // QuakePower diff --git a/src/r_utility.h b/src/r_utility.h index 10dcf5352..e9efb14e9 100644 --- a/src/r_utility.h +++ b/src/r_utility.h @@ -68,6 +68,8 @@ fixed_t R_PointToDist2 (fixed_t dx, fixed_t dy); void R_ResetViewInterpolation (); void R_RebuildViewInterpolation(player_t *player); bool R_GetViewInterpolationStatus(); +void R_ClearInterpolationPath(); +void R_AddInterpolationPoint(const fixedvec3 &vec); void R_SetViewSize (int blocks); void R_SetFOV (float fov); float R_GetFOV ();