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_user.c
p_slopes.c p_slopes.c
p_sweep.cpp p_sweep.cpp
p_test.cpp
tables.c tables.c
r_bsp.cpp r_bsp.cpp
r_data.c 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_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_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_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_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"); 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 // so missiles don't explode against sky hack walls
line_t *ceilingline; line_t *ceilingline;
// set by PIT_CheckLine() for any line that stopped the PIT_CheckLine() // P_CheckPosition: this position blocks movement
// that is, for any line which is 'solid' boolean blocking;
line_t *blockingline;
// 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; extern tm_t tm;
@ -415,6 +422,7 @@ struct TryMoveResult_t
boolean success; boolean success;
line_t *line; line_t *line;
mobj_t *mo; mobj_t *mo;
vector2_t normal;
}; };
boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *result); 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_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); 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_IsLineBlocking(const line_t *ld, const mobj_t *thing);
boolean P_IsLineTripWire(const line_t *ld); boolean P_IsLineTripWire(const line_t *ld);
boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam); 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. // could be crossed in either order.
// this line is out of the if so upper and lower textures can be hit by a splat // 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 (!ld->backsector) // one sided line
{ {
if (P_PointOnLineSide(mapcampointer->x, mapcampointer->y, ld)) if (P_PointOnLineSide(mapcampointer->x, mapcampointer->y, ld))
@ -1841,6 +1840,22 @@ boolean P_IsLineTripWire(const line_t *ld)
return ld->tripwire; 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 // PIT_CheckLine
// Adjusts tm.floorz and tm.ceilingz as lines are contacted // 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. // could be crossed in either order.
// this line is out of the if so upper and lower textures can be hit by a splat // 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)) if (P_MobjWasRemoved(tm.thing))
return BMIT_CONTINUE; // one of them was removed??? return BMIT_CONTINUE; // one of them was removed???
if (shouldCollide == 1) 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) else if (shouldCollide == 2)
return BMIT_CONTINUE; // force no collide 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)) if (P_PointOnLineSide(tm.thing->x, tm.thing->y, ld))
return BMIT_CONTINUE; // don't hit the back side 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)) 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 // set openrange, opentop, openbottom
P_LineOpening(ld, tm.thing, &open); 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 // adjust floor / ceiling heights
if (open.ceiling < tm.ceilingz) 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; tm.bbox[BOXLEFT] = x - tm.thing->radius;
newsubsec = R_PointInSubsector(x, y); newsubsec = R_PointInSubsector(x, y);
tm.ceilingline = tm.blockingline = NULL; tm.ceilingline = NULL;
tm.blocking = false;
// The base floor / ceiling is from the subsector // The base floor / ceiling is from the subsector
// that contains the point. // that contains the point.
@ -2314,23 +2376,33 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re
validcount++; validcount++;
P_ClearTestLines();
// check lines // check lines
for (bx = xl; bx <= xh; bx++) for (bx = xl; bx <= xh; bx++)
{ {
for (by = yl; by <= yh; by++) for (by = yl; by <= yh; by++)
{ {
if (!P_BlockLinesIterator(bx, by, PIT_CheckLine)) P_BlockLinesIterator(bx, by, PIT_CheckLine);
}
}
if (tm.blocking)
{ {
blockval = false; blockval = false;
} }
}
}
if (result != NULL) if (result != NULL)
{ {
result->line = tm.blockingline; result->line = NULL;
result->mo = tm.hitthing; result->mo = tm.hitthing;
} }
else
{
P_ClearTestLines();
}
tm.sweep = false;
return blockval; return blockval;
} }
@ -2379,7 +2451,7 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
tm.bbox[BOXLEFT] = x - thiscam->radius; tm.bbox[BOXLEFT] = x - thiscam->radius;
newsubsec = R_PointInSubsector(x, y); newsubsec = R_PointInSubsector(x, y);
tm.ceilingline = tm.blockingline = NULL; tm.ceilingline = NULL;
mapcampointer = thiscam; mapcampointer = thiscam;
@ -2753,22 +2825,6 @@ fixed_t P_GetThingStepUp(mobj_t *thing, fixed_t destX, fixed_t destY)
return maxstep; 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 static boolean
increment_move increment_move
( mobj_t * thing, ( mobj_t * thing,
@ -2821,7 +2877,29 @@ increment_move
tryy = y; 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 return false; // solid wall or thing
} }
@ -3466,30 +3544,27 @@ static void P_HitSlideLine(line_t *ld)
// //
// HitBounceLine, for players // 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 movelen;
fixed_t x, y; 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); movelen = P_AproxDistance(tmxmove, tmymove);
if (slidemo->player && movelen < (15*mapobjectscale)) if (slidemo->player && movelen < (15*mapobjectscale))
movelen = (15*mapobjectscale); movelen = (15*mapobjectscale);
x = FixedMul(movelen, FINECOSINE(lineangle)); if (!ld)
y = FixedMul(movelen, FINESINE(lineangle)); {
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; tmxmove = x * 4;
tmymove = y * 4; tmymove = y * 4;
@ -3958,6 +4033,8 @@ papercollision:
static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
{ {
extern consvar_t cv_showgremlins;
fixed_t mmomx = 0, mmomy = 0; fixed_t mmomx = 0, mmomy = 0;
fixed_t oldmomx = mo->momx, oldmomy = mo->momy; fixed_t oldmomx = mo->momx, oldmomy = mo->momy;
@ -3982,8 +4059,23 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
slidemo = mo; slidemo = mo;
bestslideline = result->line; bestslideline = result->line;
if (bestslideline == NULL) if (bestslideline == NULL && cv_showgremlins.value)
return; {
// 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 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))); tmymove = FixedMul(mmomy, (FRACUNIT - (FRACUNIT>>2) - (FRACUNIT>>3)));
} }
if (P_IsLineTripWire(bestslideline)) if (bestslideline && P_IsLineTripWire(bestslideline))
{ {
// TRIPWIRE CANNOT BE MADE NONBOUNCY // TRIPWIRE CANNOT BE MADE NONBOUNCY
K_ApplyTripWire(mo->player, TRIPSTATE_BLOCKED); K_ApplyTripWire(mo->player, TRIPSTATE_BLOCKED);
@ -4014,7 +4106,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
K_SpawnBumpEffect(mo); K_SpawnBumpEffect(mo);
} }
P_PlayerHitBounceLine(bestslideline); P_PlayerHitBounceLine(bestslideline, &result->normal);
mo->eflags |= MFE_JUSTBOUNCEDWALL; mo->eflags |= MFE_JUSTBOUNCEDWALL;
mo->momx = tmxmove; mo->momx = tmxmove;
@ -4022,7 +4114,7 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result)
mo->player->cmomx = tmxmove; mo->player->cmomx = tmxmove;
mo->player->cmomy = tmymove; 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)) 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 // blocked move
moved = false; moved = false;
if (LUA_HookMobjMoveBlocked(mo, tm.hitthing, tm.blockingline)) if (LUA_HookMobjMoveBlocked(mo, tm.hitthing, result.line))
{ {
if (P_MobjWasRemoved(mo)) if (P_MobjWasRemoved(mo))
return; return;
@ -1679,7 +1679,7 @@ void P_XYMovement(mobj_t *mo)
else if (P_MobjWasRemoved(mo)) else if (P_MobjWasRemoved(mo))
return; return;
P_PushSpecialLine(tm.blockingline, mo); P_PushSpecialLine(result.line, mo);
if (mo->flags & MF_MISSILE) 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();
}