P_TryMove: sweep collided lines to find nearest normal

This commit is contained in:
James R 2023-11-10 00:02:32 -08:00
parent 5a62a07e54
commit 7861d51a7f
6 changed files with 238 additions and 54 deletions

View file

@ -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

View file

@ -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");

View file

@ -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);

View file

@ -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))
{

View file

@ -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)
{

78
src/p_test.cpp Normal file
View file

@ -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 <optional>
#include <vector>
#include "math/fixed.hpp"
#include "p_sweep.hpp"
#include "p_local.h"
namespace
{
std::vector<line_t*> 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> collision;
LineSegment<Fixed> l{{ax, ay}, {bx, by}};
AABBvsLine sweep{r, l};
for (line_t* ld : g_lines)
{
LineSegment<Fixed> 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();
}