From 7861d51a7f39848d9478739cbd396fba4637ae82 Mon Sep 17 00:00:00 2001 From: James R Date: Fri, 10 Nov 2023 00:02:32 -0800 Subject: [PATCH] P_TryMove: sweep collided lines to find nearest normal --- src/CMakeLists.txt | 1 + src/cvars.cpp | 1 + src/p_local.h | 18 ++++- src/p_map.c | 190 +++++++++++++++++++++++++++++++++------------ src/p_mobj.c | 4 +- src/p_test.cpp | 78 +++++++++++++++++++ 6 files changed, 238 insertions(+), 54 deletions(-) create mode 100644 src/p_test.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f088dae7..a1f3d29e6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,6 +65,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 p_user.c p_slopes.c p_sweep.cpp + p_test.cpp tables.c r_bsp.cpp r_data.c diff --git a/src/cvars.cpp b/src/cvars.cpp index 4b0897dcf..9336becb2 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -806,6 +806,7 @@ consvar_t cv_numlaps = OnlineCheat("numlaps", "Map default").values(numlaps_cons consvar_t cv_restrictskinchange = OnlineCheat("restrictskinchange", "Yes").yes_no().description("Don't let players change their skin in the middle of gameplay"); consvar_t cv_spbtest = OnlineCheat("spbtest", "Off").on_off().description("SPB can never target a player"); +consvar_t cv_showgremlins = OnlineCheat("showgremlins", "No").yes_no().description("Show line collision errors"); consvar_t cv_timescale = OnlineCheat(cvlist_timer)("timescale", "1.0").floating_point().min_max(FRACUNIT/20, 20*FRACUNIT).description("Overclock or slow down the game"); consvar_t cv_ufo_follow = OnlineCheat("ufo_follow", "0").min_max(0, MAXPLAYERS).description("Make UFO Catcher folow this player"); consvar_t cv_ufo_health = OnlineCheat("ufo_health", "-1").min_max(-1, 100).description("Override UFO Catcher health -- applied at spawn or when value is changed"); diff --git a/src/p_local.h b/src/p_local.h index 0e0a3fb23..045a59472 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -387,9 +387,16 @@ struct tm_t // so missiles don't explode against sky hack walls line_t *ceilingline; - // set by PIT_CheckLine() for any line that stopped the PIT_CheckLine() - // that is, for any line which is 'solid' - line_t *blockingline; + // P_CheckPosition: this position blocks movement + boolean blocking; + + // P_CheckPosition: set this before each call to + // P_CheckPosition to enable a line sweep on collided + // lines + boolean sweep; + + // sweep: max step up at tm.x, tm.y + fixed_t maxstep; }; extern tm_t tm; @@ -415,6 +422,7 @@ struct TryMoveResult_t boolean success; line_t *line; mobj_t *mo; + vector2_t normal; }; boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *result); @@ -422,6 +430,10 @@ boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff, T boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff, TryMoveResult_t *result); boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *result); +void P_TestLine(line_t *ld); +void P_ClearTestLines(void); +line_t *P_SweepTestLines(fixed_t ax, fixed_t ay, fixed_t bx, fixed_t by, fixed_t r, vector2_t *return_normal); + boolean P_IsLineBlocking(const line_t *ld, const mobj_t *thing); boolean P_IsLineTripWire(const line_t *ld); boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam); diff --git a/src/p_map.c b/src/p_map.c index 79b9ba59e..683dd3c5d 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1770,7 +1770,6 @@ static BlockItReturn_t PIT_CheckCameraLine(line_t *ld) // could be crossed in either order. // this line is out of the if so upper and lower textures can be hit by a splat - tm.blockingline = ld; if (!ld->backsector) // one sided line { if (P_PointOnLineSide(mapcampointer->x, mapcampointer->y, ld)) @@ -1841,6 +1840,22 @@ boolean P_IsLineTripWire(const line_t *ld) return ld->tripwire; } +static boolean P_UsingStepUp(mobj_t *thing) +{ + if (thing->flags & MF_NOCLIP) + { + return false; + } + + // orbits have no collision + if (thing->player && thing->player->loop.radius) + { + return false; + } + + return true; +} + // // PIT_CheckLine // Adjusts tm.floorz and tm.ceilingz as lines are contacted @@ -1898,14 +1913,20 @@ static BlockItReturn_t PIT_CheckLine(line_t *ld) // could be crossed in either order. // this line is out of the if so upper and lower textures can be hit by a splat - tm.blockingline = ld; { - UINT8 shouldCollide = LUA_HookMobjLineCollide(tm.thing, tm.blockingline); // checks hook for thing's type + UINT8 shouldCollide = LUA_HookMobjLineCollide(tm.thing, ld); // checks hook for thing's type if (P_MobjWasRemoved(tm.thing)) return BMIT_CONTINUE; // one of them was removed??? if (shouldCollide == 1) - return BMIT_ABORT; // force collide + { + if (tm.sweep) + { + P_TestLine(ld); + } + tm.blocking = true; // force collide + return BMIT_CONTINUE; + } else if (shouldCollide == 2) return BMIT_CONTINUE; // force no collide } @@ -1914,15 +1935,55 @@ static BlockItReturn_t PIT_CheckLine(line_t *ld) { if (P_PointOnLineSide(tm.thing->x, tm.thing->y, ld)) return BMIT_CONTINUE; // don't hit the back side - return BMIT_ABORT; + + if (tm.sweep) + { + P_TestLine(ld); + } + tm.blocking = true; + return BMIT_CONTINUE; } if (P_IsLineBlocking(ld, tm.thing)) - return BMIT_ABORT; + { + if (tm.sweep) + { + P_TestLine(ld); + } + tm.blocking = true; + return BMIT_CONTINUE; + } // set openrange, opentop, openbottom P_LineOpening(ld, tm.thing, &open); + if (tm.sweep && P_UsingStepUp(tm.thing)) + { + // copied from P_TryMove + // TODO: refactor this into one place + if (open.range < tm.thing->height) + { + P_TestLine(ld); + } + else if (tm.maxstep > 0) + { + if (tm.thing->z < open.floor) + { + if (open.floorstep > tm.maxstep) + { + P_TestLine(ld); + } + } + else if (open.ceiling < tm.thing->z + tm.thing->height) + { + if (open.ceilingstep > tm.maxstep) + { + P_TestLine(ld); + } + } + } + } + // adjust floor / ceiling heights if (open.ceiling < tm.ceilingz) { @@ -2042,7 +2103,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re tm.bbox[BOXLEFT] = x - tm.thing->radius; newsubsec = R_PointInSubsector(x, y); - tm.ceilingline = tm.blockingline = NULL; + tm.ceilingline = NULL; + tm.blocking = false; // The base floor / ceiling is from the subsector // that contains the point. @@ -2314,23 +2376,33 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re validcount++; + P_ClearTestLines(); + // check lines for (bx = xl; bx <= xh; bx++) { for (by = yl; by <= yh; by++) { - if (!P_BlockLinesIterator(bx, by, PIT_CheckLine)) - { - blockval = false; - } + P_BlockLinesIterator(bx, by, PIT_CheckLine); } } + if (tm.blocking) + { + blockval = false; + } + if (result != NULL) { - result->line = tm.blockingline; + result->line = NULL; result->mo = tm.hitthing; } + else + { + P_ClearTestLines(); + } + + tm.sweep = false; return blockval; } @@ -2379,7 +2451,7 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam) tm.bbox[BOXLEFT] = x - thiscam->radius; newsubsec = R_PointInSubsector(x, y); - tm.ceilingline = tm.blockingline = NULL; + tm.ceilingline = NULL; mapcampointer = thiscam; @@ -2753,22 +2825,6 @@ fixed_t P_GetThingStepUp(mobj_t *thing, fixed_t destX, fixed_t destY) return maxstep; } -static boolean P_UsingStepUp(mobj_t *thing) -{ - if (thing->flags & MF_NOCLIP) - { - return false; - } - - // orbits have no collision - if (thing->player && thing->player->loop.radius) - { - return false; - } - - return true; -} - static boolean increment_move ( mobj_t * thing, @@ -2821,7 +2877,29 @@ increment_move tryy = y; } - if (!P_CheckPosition(thing, tryx, tryy, result)) + if (P_UsingStepUp(thing)) + { + tm.maxstep = P_GetThingStepUp(thing, tryx, tryy); + } + + if (result) + { + tm.sweep = true; + } + + boolean move_ok = P_CheckPosition(thing, tryx, tryy, result); + + if (P_MobjWasRemoved(thing)) + { + return false; + } + + if (result) + { + result->line = P_SweepTestLines(thing->x, thing->y, x, y, thing->radius, &result->normal); + } + + if (!move_ok) { return false; // solid wall or thing } @@ -3466,30 +3544,27 @@ static void P_HitSlideLine(line_t *ld) // // HitBounceLine, for players // -static void P_PlayerHitBounceLine(line_t *ld) +static void P_PlayerHitBounceLine(line_t *ld, vector2_t* normal) { - INT32 side; - angle_t lineangle; fixed_t movelen; fixed_t x, y; - side = P_PointOnLineSide(slidemo->x, slidemo->y, ld); - lineangle = ld->angle - ANGLE_90; - - if (side == 1) - lineangle += ANGLE_180; - - lineangle >>= ANGLETOFINESHIFT; - movelen = P_AproxDistance(tmxmove, tmymove); if (slidemo->player && movelen < (15*mapobjectscale)) movelen = (15*mapobjectscale); - x = FixedMul(movelen, FINECOSINE(lineangle)); - y = FixedMul(movelen, FINESINE(lineangle)); + if (!ld) + { + angle_t th = R_PointToAngle2(0, 0, tmxmove, tmymove); + normal->x = -FCOS(th); + normal->y = -FSIN(th); + } - if (P_IsLineTripWire(ld)) + x = FixedMul(movelen, normal->x); + y = FixedMul(movelen, normal->y); + + if (ld && P_IsLineTripWire(ld)) { tmxmove = x * 4; tmymove = y * 4; @@ -3958,6 +4033,8 @@ papercollision: static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) { + extern consvar_t cv_showgremlins; + fixed_t mmomx = 0, mmomy = 0; fixed_t oldmomx = mo->momx, oldmomy = mo->momy; @@ -3982,8 +4059,23 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) slidemo = mo; bestslideline = result->line; - if (bestslideline == NULL) - return; + if (bestslideline == NULL && cv_showgremlins.value) + { + // debug + mobj_t*x = P_SpawnMobj(mo->x, mo->y, mo->z, MT_THOK); + x->frame = FF_FULLBRIGHT | FF_ADD; + x->renderflags = RF_ALWAYSONTOP; + x->color = SKINCOLOR_RED; + + CONS_Printf( + "GREMLIN: leveltime=%u x=%f y=%f z=%f angle=%f\n", + leveltime, + FixedToFloat(mo->x), + FixedToFloat(mo->y), + FixedToFloat(mo->z), + AngleToFloat(R_PointToAngle2(0, 0, oldmomx, oldmomy)) + ); + } if (mo->eflags & MFE_JUSTBOUNCEDWALL) // Stronger push-out { @@ -3996,7 +4088,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3))); } - if (P_IsLineTripWire(bestslideline)) + if (bestslideline && P_IsLineTripWire(bestslideline)) { // TRIPWIRE CANNOT BE MADE NONBOUNCY K_ApplyTripWire(mo->player, TRIPSTATE_BLOCKED); @@ -4014,7 +4106,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) K_SpawnBumpEffect(mo); } - P_PlayerHitBounceLine(bestslideline); + P_PlayerHitBounceLine(bestslideline, &result->normal); mo->eflags |= MFE_JUSTBOUNCEDWALL; mo->momx = tmxmove; @@ -4022,7 +4114,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) mo->player->cmomx = tmxmove; mo->player->cmomy = tmymove; - if (!P_IsLineTripWire(bestslideline)) + if (!bestslideline || !P_IsLineTripWire(bestslideline)) { if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true, NULL)) { diff --git a/src/p_mobj.c b/src/p_mobj.c index f0baa74e1..447592037 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1671,7 +1671,7 @@ void P_XYMovement(mobj_t *mo) // blocked move moved = false; - if (LUA_HookMobjMoveBlocked(mo, tm.hitthing, tm.blockingline)) + if (LUA_HookMobjMoveBlocked(mo, tm.hitthing, result.line)) { if (P_MobjWasRemoved(mo)) return; @@ -1679,7 +1679,7 @@ void P_XYMovement(mobj_t *mo) else if (P_MobjWasRemoved(mo)) return; - P_PushSpecialLine(tm.blockingline, mo); + P_PushSpecialLine(result.line, mo); if (mo->flags & MF_MISSILE) { diff --git a/src/p_test.cpp b/src/p_test.cpp new file mode 100644 index 000000000..e362f33a5 --- /dev/null +++ b/src/p_test.cpp @@ -0,0 +1,78 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include +#include + +#include "math/fixed.hpp" +#include "p_sweep.hpp" + +#include "p_local.h" + +namespace +{ + +std::vector g_lines; + +}; + +void P_TestLine(line_t* ld) +{ + g_lines.emplace_back(ld); +} + +line_t* P_SweepTestLines(fixed_t ax, fixed_t ay, fixed_t bx, fixed_t by, fixed_t r, vector2_t* return_normal) +{ + using namespace srb2::math; + using namespace srb2::sweep; + + struct Collision + { + unit z; + vec2 normal; + line_t* ld; + + bool operator<(const Collision& b) const { return z < b.z; } + }; + + std::optional collision; + + LineSegment l{{ax, ay}, {bx, by}}; + AABBvsLine sweep{r, l}; + + for (line_t* ld : g_lines) + { + LineSegment ls{{ld->v1->x, ld->v1->y}, {ld->v2->x, ld->v2->y}}; + Result rs = sweep(ls); + if (rs.hit) + { + if (!collision || rs.hit->z < collision->z) + { + collision = {rs.hit->z, rs.hit->n, ld}; + } + } + } + + g_lines.clear(); + + if (!collision) + { + return nullptr; + } + + return_normal->x = Fixed {collision->normal.x}; + return_normal->y = Fixed {collision->normal.y}; + + return collision->ld; +} + +void P_ClearTestLines(void) +{ + g_lines.clear(); +}