mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2024-12-28 15:45:41 +03:00
1097 lines
27 KiB
C++
1097 lines
27 KiB
C++
#include "precompiled.h"
|
|
|
|
// Determine actual path positions
|
|
bool CNavPath::ComputePathPositions()
|
|
{
|
|
if (m_segmentCount == 0)
|
|
return false;
|
|
|
|
// start in first area's center
|
|
m_path[0].pos = *m_path[0].area->GetCenter();
|
|
m_path[0].ladder = nullptr;
|
|
m_path[0].how = NUM_TRAVERSE_TYPES;
|
|
|
|
for (int i = 1; i < m_segmentCount; i++)
|
|
{
|
|
const PathSegment *from = &m_path[i - 1];
|
|
PathSegment *to = &m_path[i];
|
|
|
|
// walk along the floor to the next area
|
|
if (to->how <= GO_WEST)
|
|
{
|
|
to->ladder = nullptr;
|
|
|
|
// compute next point, keeping path as straight as possible
|
|
from->area->ComputeClosestPointInPortal(to->area, (NavDirType)to->how, &from->pos, &to->pos);
|
|
|
|
// move goal position into the goal area a bit
|
|
// how far to "step into" an area - must be less than min area size
|
|
const float stepInDist = 5.0f;
|
|
AddDirectionVector(&to->pos, (NavDirType)to->how, stepInDist);
|
|
|
|
// we need to walk out of "from" area, so keep Z where we can reach it
|
|
to->pos.z = from->area->GetZ(&to->pos);
|
|
|
|
// if this is a "jump down" connection, we must insert an additional point on the path
|
|
if (to->area->IsConnected(from->area, NUM_DIRECTIONS) == false)
|
|
{
|
|
// this is a "jump down" link
|
|
// compute direction of path just prior to "jump down"
|
|
Vector2D dir;
|
|
DirectionToVector2D((NavDirType)to->how, &dir);
|
|
|
|
// shift top of "jump down" out a bit to "get over the ledge"
|
|
const float pushDist = 25.0f;
|
|
to->pos.x += pushDist * dir.x;
|
|
to->pos.y += pushDist * dir.y;
|
|
|
|
// insert a duplicate node to represent the bottom of the fall
|
|
if (m_segmentCount < MAX_PATH_SEGMENTS - 1)
|
|
{
|
|
// copy nodes down
|
|
for (int j = m_segmentCount; j > i; j--)
|
|
m_path[j] = m_path[j - 1];
|
|
|
|
// path is one node longer
|
|
m_segmentCount++;
|
|
|
|
// move index ahead into the new node we just duplicated
|
|
i++;
|
|
|
|
m_path[i].pos.x = to->pos.x + pushDist * dir.x;
|
|
m_path[i].pos.y = to->pos.y + pushDist * dir.y;
|
|
|
|
// put this one at the bottom of the fall
|
|
m_path[i].pos.z = to->area->GetZ(&m_path[i].pos);
|
|
}
|
|
}
|
|
}
|
|
// to get to next area, must go up a ladder
|
|
else if (to->how == GO_LADDER_UP)
|
|
{
|
|
// find our ladder
|
|
const NavLadderList *list = from->area->GetLadderList(LADDER_UP);
|
|
NavLadderList::const_iterator iter;
|
|
for (iter = list->begin(); iter != list->end(); iter++)
|
|
{
|
|
CNavLadder *ladder = (*iter);
|
|
|
|
// can't use "behind" area when ascending...
|
|
if (ladder->m_topForwardArea == to->area || ladder->m_topLeftArea == to->area || ladder->m_topRightArea == to->area)
|
|
{
|
|
to->ladder = ladder;
|
|
to->pos = ladder->m_bottom;
|
|
AddDirectionVector(&to->pos, ladder->m_dir, 2.0f * HalfHumanWidth);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (iter == list->end())
|
|
{
|
|
//PrintIfWatched( "ERROR: Can't find ladder in path\n" );
|
|
return false;
|
|
}
|
|
}
|
|
// to get to next area, must go down a ladder
|
|
else if (to->how == GO_LADDER_DOWN)
|
|
{
|
|
// find our ladder
|
|
const NavLadderList *list = from->area->GetLadderList(LADDER_DOWN);
|
|
NavLadderList::const_iterator iter;
|
|
for (iter = list->begin(); iter != list->end(); iter++)
|
|
{
|
|
CNavLadder *ladder = (*iter);
|
|
|
|
if (ladder->m_bottomArea == to->area)
|
|
{
|
|
to->ladder = ladder;
|
|
to->pos = ladder->m_top;
|
|
AddDirectionVector(&to->pos, OppositeDirection(ladder->m_dir), 2.0f * HalfHumanWidth);
|
|
break;
|
|
}
|
|
}
|
|
if (iter == list->end())
|
|
{
|
|
//PrintIfWatched( "ERROR: Can't find ladder in path\n" );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Return true if position is at the end of the path
|
|
bool CNavPath::IsAtEnd(const Vector &pos) const
|
|
{
|
|
if (!IsValid())
|
|
return false;
|
|
|
|
const float epsilon = 20.0f;
|
|
return (pos - GetEndpoint()).IsLengthLessThan(epsilon);
|
|
}
|
|
|
|
// Return length of path from start to finish
|
|
float CNavPath::GetLength() const
|
|
{
|
|
float length = 0.0f;
|
|
for (int i = 1; i < GetSegmentCount(); i++)
|
|
{
|
|
length += (m_path[i].pos - m_path[i - 1].pos).Length();
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
// Return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end
|
|
// TODO: Be careful of returning "positions" along one-way drops, ladders, etc.
|
|
NOXREF bool CNavPath::GetPointAlongPath(float distAlong, Vector *pointOnPath) const
|
|
{
|
|
if (!IsValid() || !pointOnPath)
|
|
return false;
|
|
|
|
if (distAlong <= 0.0f)
|
|
{
|
|
*pointOnPath = m_path[0].pos;
|
|
return true;
|
|
}
|
|
|
|
float lengthSoFar = 0.0f;
|
|
float segmentLength;
|
|
Vector dir;
|
|
for (int i = 1; i < GetSegmentCount(); i++)
|
|
{
|
|
dir = m_path[i].pos - m_path[i - 1].pos;
|
|
segmentLength = dir.Length();
|
|
|
|
if (segmentLength + lengthSoFar >= distAlong)
|
|
{
|
|
// desired point is on this segment of the path
|
|
float delta = distAlong - lengthSoFar;
|
|
float t = delta / segmentLength;
|
|
|
|
*pointOnPath = m_path[i].pos + t * dir;
|
|
|
|
return true;
|
|
}
|
|
|
|
lengthSoFar += segmentLength;
|
|
}
|
|
|
|
*pointOnPath = m_path[GetSegmentCount() - 1].pos;
|
|
return true;
|
|
}
|
|
|
|
// Return the node index closest to the given distance along the path without going over - returns (-1) if error
|
|
int CNavPath::GetSegmentIndexAlongPath(float distAlong) const
|
|
{
|
|
if (!IsValid())
|
|
return -1;
|
|
|
|
if (distAlong <= 0.0f)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
float lengthSoFar = 0.0f;
|
|
Vector dir;
|
|
for (int i = 1; i < GetSegmentCount(); i++)
|
|
{
|
|
lengthSoFar += (m_path[i].pos - m_path[i - 1].pos).Length();
|
|
|
|
if (lengthSoFar > distAlong)
|
|
{
|
|
return i - 1;
|
|
}
|
|
}
|
|
|
|
return GetSegmentCount() - 1;
|
|
}
|
|
|
|
// Compute closest point on path to given point
|
|
// NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc
|
|
NOXREF bool CNavPath::FindClosestPointOnPath(const Vector *worldPos, int startIndex, int endIndex, Vector *close) const
|
|
{
|
|
if (!IsValid() || !close)
|
|
return false;
|
|
|
|
Vector along, toWorldPos;
|
|
Vector pos;
|
|
const Vector *from, *to;
|
|
float length;
|
|
float closeLength;
|
|
float closeDistSq = 9999999999.9;
|
|
float distSq;
|
|
|
|
for (int i = startIndex; i <= endIndex; i++)
|
|
{
|
|
from = &m_path[i - 1].pos;
|
|
to = &m_path[i].pos;
|
|
|
|
// compute ray along this path segment
|
|
along = *to - *from;
|
|
|
|
// make it a unit vector along the path
|
|
length = along.NormalizeInPlace();
|
|
|
|
// compute vector from start of segment to our point
|
|
toWorldPos = *worldPos - *from;
|
|
|
|
// find distance of closest point on ray
|
|
closeLength = DotProduct(toWorldPos, along);
|
|
|
|
// constrain point to be on path segment
|
|
if (closeLength <= 0.0f)
|
|
pos = *from;
|
|
else if (closeLength >= length)
|
|
pos = *to;
|
|
else
|
|
pos = *from + closeLength * along;
|
|
|
|
distSq = (pos - *worldPos).LengthSquared();
|
|
|
|
// keep the closest point so far
|
|
if (distSq < closeDistSq)
|
|
{
|
|
closeDistSq = distSq;
|
|
*close = pos;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Build trivial path when start and goal are in the same nav area
|
|
bool CNavPath::BuildTrivialPath(const Vector *start, const Vector *goal)
|
|
{
|
|
m_segmentCount = 0;
|
|
|
|
CNavArea *startArea = TheNavAreaGrid.GetNearestNavArea(start);
|
|
if (!startArea)
|
|
return false;
|
|
|
|
CNavArea *goalArea = TheNavAreaGrid.GetNearestNavArea(goal);
|
|
if (!goalArea)
|
|
return false;
|
|
|
|
m_segmentCount = 2;
|
|
|
|
m_path[0].area = startArea;
|
|
m_path[0].pos.x = start->x;
|
|
m_path[0].pos.y = start->y;
|
|
m_path[0].pos.z = startArea->GetZ(start);
|
|
m_path[0].ladder = nullptr;
|
|
m_path[0].how = NUM_TRAVERSE_TYPES;
|
|
|
|
m_path[1].area = goalArea;
|
|
m_path[1].pos.x = goal->x;
|
|
m_path[1].pos.y = goal->y;
|
|
m_path[1].pos.z = goalArea->GetZ(goal);
|
|
m_path[1].ladder = nullptr;
|
|
m_path[1].how = NUM_TRAVERSE_TYPES;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Draw the path for debugging
|
|
void CNavPath::Draw()
|
|
{
|
|
if (!IsValid())
|
|
return;
|
|
|
|
for (int i = 1; i < m_segmentCount; i++)
|
|
{
|
|
UTIL_DrawBeamPoints(m_path[i - 1].pos + Vector(0, 0, HalfHumanHeight), m_path[i].pos + Vector(0, 0, HalfHumanHeight), 2, 255, 75, 0);
|
|
}
|
|
}
|
|
|
|
// Check line of sight from 'anchor' node on path to subsequent nodes until
|
|
// we find a node that can't been seen from 'anchor'
|
|
int CNavPath::FindNextOccludedNode(int anchor)
|
|
{
|
|
int lastVisible = anchor;
|
|
for (int i = anchor + 1; i < m_segmentCount; i++)
|
|
{
|
|
// don't remove ladder nodes
|
|
if (m_path[i].ladder)
|
|
return i;
|
|
|
|
if (!IsWalkableTraceLineClear(m_path[anchor].pos, m_path[i].pos))
|
|
{
|
|
// cant see this node from anchor node
|
|
return i;
|
|
}
|
|
|
|
Vector anchorPlusHalf = m_path[anchor].pos + Vector(0, 0, HalfHumanHeight);
|
|
Vector iPlusHalf = m_path[i].pos + Vector(0, 0, HalfHumanHeight);
|
|
if (!IsWalkableTraceLineClear(anchorPlusHalf, iPlusHalf))
|
|
{
|
|
// cant see this node from anchor node
|
|
return i;
|
|
}
|
|
|
|
Vector anchorPlusFull = m_path[anchor].pos + Vector(0, 0, HumanHeight);
|
|
Vector iPlusFull = m_path[i].pos + Vector(0, 0, HumanHeight);
|
|
if (!IsWalkableTraceLineClear(anchorPlusFull, iPlusFull))
|
|
{
|
|
// cant see this node from anchor node
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return m_segmentCount;
|
|
}
|
|
|
|
// Smooth out path, removing redundant nodes
|
|
void CNavPath::Optimize()
|
|
{
|
|
// DONT USE THIS: Optimizing the path results in cutting thru obstacles
|
|
#if 0
|
|
if (m_segmentCount < 3)
|
|
return;
|
|
|
|
int anchor = 0;
|
|
while (anchor < m_segmentCount)
|
|
{
|
|
int occluded = FindNextOccludedNode(anchor);
|
|
int nextAnchor = occluded - 1;
|
|
|
|
if (nextAnchor > anchor)
|
|
{
|
|
// remove redundant nodes between anchor and nextAnchor
|
|
int removeCount = nextAnchor - anchor - 1;
|
|
if (removeCount > 0)
|
|
{
|
|
for (int i = nextAnchor; i < m_segmentCount; i++)
|
|
{
|
|
m_path[i - removeCount] = m_path[i];
|
|
}
|
|
m_segmentCount -= removeCount;
|
|
}
|
|
}
|
|
|
|
anchor++;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
CNavPathFollower::CNavPathFollower()
|
|
{
|
|
m_improv = nullptr;
|
|
m_path = nullptr;
|
|
|
|
m_segmentIndex = 0;
|
|
m_isLadderStarted = false;
|
|
|
|
m_isDebug = false;
|
|
}
|
|
|
|
void CNavPathFollower::Reset()
|
|
{
|
|
m_segmentIndex = 1;
|
|
m_isLadderStarted = false;
|
|
|
|
m_stuckMonitor.Reset();
|
|
}
|
|
|
|
// Move improv along path
|
|
void CNavPathFollower::Update(float deltaT, bool avoidObstacles)
|
|
{
|
|
if (!m_path || m_path->IsValid() == false)
|
|
return;
|
|
|
|
const CNavPath::PathSegment *node = (*m_path)[m_segmentIndex];
|
|
|
|
if (!node)
|
|
{
|
|
m_improv->OnMoveToFailure(m_path->GetEndpoint(), IImprovEvent::FAIL_INVALID_PATH);
|
|
m_path->Invalidate();
|
|
return;
|
|
}
|
|
|
|
// handle ladders
|
|
if (node->ladder)
|
|
{
|
|
const Vector *approachPos = nullptr;
|
|
const Vector *departPos = nullptr;
|
|
|
|
if (m_segmentIndex)
|
|
approachPos = &(*m_path)[m_segmentIndex - 1]->pos;
|
|
|
|
if (m_segmentIndex < m_path->GetSegmentCount() - 1)
|
|
departPos = &(*m_path)[m_segmentIndex + 1]->pos;
|
|
|
|
if (!m_isLadderStarted)
|
|
{
|
|
// set up ladder movement
|
|
m_improv->StartLadder(node->ladder, node->how, approachPos, departPos);
|
|
m_isLadderStarted = true;
|
|
}
|
|
|
|
// move improv along ladder
|
|
if (m_improv->TraverseLadder(node->ladder, node->how, approachPos, departPos, deltaT))
|
|
{
|
|
// completed ladder
|
|
m_segmentIndex++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// reset ladder init flag
|
|
m_isLadderStarted = false;
|
|
|
|
// Check if we reached the end of the path
|
|
const float closeRange = 20.0f;
|
|
if ((m_improv->GetFeet() - node->pos).IsLengthLessThan(closeRange))
|
|
{
|
|
m_segmentIndex++;
|
|
|
|
if (m_segmentIndex >= m_path->GetSegmentCount())
|
|
{
|
|
m_improv->OnMoveToSuccess(m_path->GetEndpoint());
|
|
m_path->Invalidate();
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_goal = node->pos;
|
|
|
|
const float aheadRange = 300.0f;
|
|
m_segmentIndex = FindPathPoint(aheadRange, &m_goal, &m_behindIndex);
|
|
if (m_segmentIndex >= m_path->GetSegmentCount())
|
|
m_segmentIndex = m_path->GetSegmentCount() - 1;
|
|
|
|
bool isApproachingJumpArea = false;
|
|
|
|
// Crouching
|
|
if (!m_improv->IsUsingLadder())
|
|
{
|
|
// because hostage crouching is not really supported by the engine,
|
|
// if we are standing in a crouch area, we must crouch to avoid collisions
|
|
if (m_improv->GetLastKnownArea() && (m_improv->GetLastKnownArea()->GetAttributes() & NAV_CROUCH) && !(m_improv->GetLastKnownArea()->GetAttributes() & NAV_JUMP))
|
|
{
|
|
m_improv->Crouch();
|
|
}
|
|
|
|
// if we are approaching a crouch area, crouch
|
|
// if there are no crouch areas coming up, stand
|
|
const float crouchRange = 50.0f;
|
|
bool didCrouch = false;
|
|
for (int i = m_segmentIndex; i < m_path->GetSegmentCount(); i++)
|
|
{
|
|
const CNavArea *to = (*m_path)[i]->area;
|
|
|
|
// if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump
|
|
if (to->GetAttributes() & NAV_JUMP)
|
|
{
|
|
isApproachingJumpArea = true;
|
|
break;
|
|
}
|
|
|
|
Vector close;
|
|
to->GetClosestPointOnArea(&m_improv->GetCentroid(), &close);
|
|
|
|
if ((close - m_improv->GetFeet()).Make2D().IsLengthGreaterThan(crouchRange))
|
|
break;
|
|
|
|
if (to->GetAttributes() & NAV_CROUCH)
|
|
{
|
|
m_improv->Crouch();
|
|
didCrouch = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!didCrouch && !m_improv->IsJumping())
|
|
{
|
|
// no crouch areas coming up
|
|
m_improv->StandUp();
|
|
}
|
|
}
|
|
// end crouching logic
|
|
|
|
if (m_isDebug)
|
|
{
|
|
m_path->Draw();
|
|
UTIL_DrawBeamPoints(m_improv->GetCentroid(), m_goal + Vector(0, 0, StepHeight), 1, 255, 0, 255);
|
|
UTIL_DrawBeamPoints(m_goal + Vector(0, 0, StepHeight), m_improv->GetCentroid(), 1, 255, 0, 255);
|
|
}
|
|
|
|
// check if improv becomes stuck
|
|
m_stuckMonitor.Update(m_improv);
|
|
|
|
// if improv has been stuck for too long, give up
|
|
const float giveUpTime = 2.0f;
|
|
if (m_stuckMonitor.GetDuration() > giveUpTime)
|
|
{
|
|
m_improv->OnMoveToFailure(m_path->GetEndpoint(), IImprovEvent::FAIL_STUCK);
|
|
m_path->Invalidate();
|
|
return;
|
|
}
|
|
|
|
// if our goal is high above us, we must have fallen
|
|
if (m_goal.z - m_improv->GetFeet().z > JumpCrouchHeight)
|
|
{
|
|
const float closeRange = 75.0f;
|
|
Vector2D to(m_improv->GetFeet().x - m_goal.x, m_improv->GetFeet().y - m_goal.y);
|
|
|
|
if (to.IsLengthLessThan(closeRange))
|
|
{
|
|
// we can't reach the goal position
|
|
// check if we can reach the next node, in case this was a "jump down" situation
|
|
const CNavPath::PathSegment *nextNode = (*m_path)[m_behindIndex + 1];
|
|
if (m_behindIndex >= 0 && nextNode)
|
|
{
|
|
if (nextNode->pos.z - m_improv->GetFeet().z > JumpCrouchHeight)
|
|
{
|
|
// the next node is too high, too - we really did fall of the path
|
|
m_improv->OnMoveToFailure(m_path->GetEndpoint(), IImprovEvent::FAIL_FELL_OFF);
|
|
m_path->Invalidate();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// fell trying to get to the last node in the path
|
|
m_improv->OnMoveToFailure(m_path->GetEndpoint(), IImprovEvent::FAIL_FELL_OFF);
|
|
m_path->Invalidate();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// avoid small obstacles
|
|
if (avoidObstacles && !isApproachingJumpArea && !m_improv->IsJumping() && m_segmentIndex < m_path->GetSegmentCount() - 1)
|
|
{
|
|
FeelerReflexAdjustment(&m_goal);
|
|
|
|
// currently, this is only used for hostages, and their collision physics stinks
|
|
// do more feeler checks to avoid short obstacles
|
|
/*
|
|
const float inc = 0.25f;
|
|
for (float t = 0.5f; t < 1.0f; t += inc)
|
|
{
|
|
FeelerReflexAdjustment(&m_goal, t * StepHeight);
|
|
}
|
|
*/
|
|
}
|
|
|
|
// move improv along path
|
|
m_improv->TrackPath(m_goal, deltaT);
|
|
}
|
|
|
|
// Return the closest point to our current position on our current path
|
|
// If "local" is true, only check the portion of the path surrounding m_pathIndex
|
|
int CNavPathFollower::FindOurPositionOnPath(Vector *close, bool local) const
|
|
{
|
|
Vector along, toFeet;
|
|
Vector feet = m_improv->GetFeet();
|
|
Vector eyes = m_improv->GetEyes();
|
|
Vector pos;
|
|
const Vector *from, *to;
|
|
real_t length;
|
|
float closeLength;
|
|
float closeDistSq = 1.0e10;
|
|
int closeIndex = -1;
|
|
real_t distSq;
|
|
int start, end;
|
|
|
|
if (!m_path->IsValid())
|
|
return -1;
|
|
|
|
if (local)
|
|
{
|
|
start = m_segmentIndex - 3;
|
|
if (start < 1)
|
|
start = 1;
|
|
|
|
end = m_segmentIndex + 3;
|
|
if (end > m_path->GetSegmentCount())
|
|
end = m_path->GetSegmentCount();
|
|
}
|
|
else
|
|
{
|
|
start = 1;
|
|
end = m_path->GetSegmentCount();
|
|
}
|
|
|
|
for (int i = start; i < end; i++)
|
|
{
|
|
from = &(*m_path)[i - 1]->pos;
|
|
to = &(*m_path)[i]->pos;
|
|
|
|
// compute ray along this path segment
|
|
along = *to - *from;
|
|
|
|
// make it a unit vector along the path
|
|
length = along.NormalizeInPlace();
|
|
|
|
// compute vector from start of segment to our point
|
|
toFeet = feet - *from;
|
|
|
|
// find distance of closest point on ray
|
|
closeLength = DotProduct(toFeet, along);
|
|
|
|
// constrain point to be on path segment
|
|
if (closeLength <= 0.0f)
|
|
pos = *from;
|
|
else if (closeLength >= length)
|
|
pos = *to;
|
|
else
|
|
pos = *from + closeLength * along;
|
|
|
|
distSq = (pos - feet).LengthSquared();
|
|
|
|
// keep the closest point so far
|
|
if (distSq < closeDistSq)
|
|
{
|
|
// don't use points we cant see
|
|
Vector probe = pos + Vector(0, 0, HalfHumanHeight);
|
|
if (!IsWalkableTraceLineClear(eyes, probe, WALK_THRU_DOORS | WALK_THRU_BREAKABLES))
|
|
continue;
|
|
|
|
// don't use points we cant reach
|
|
//if (!IsStraightLinePathWalkable(&pos))
|
|
// continue;
|
|
|
|
closeDistSq = distSq;
|
|
if (close)
|
|
*close = pos;
|
|
closeIndex = i - 1;
|
|
}
|
|
}
|
|
|
|
return closeIndex;
|
|
}
|
|
|
|
// Compute a point a fixed distance ahead along our path
|
|
// Returns path index just after point
|
|
int CNavPathFollower::FindPathPoint(float aheadRange, Vector *point, int *prevIndex)
|
|
{
|
|
// find path index just past aheadRange
|
|
int afterIndex;
|
|
|
|
// finds the closest point on local area of path, and returns the path index just prior to it
|
|
Vector close;
|
|
int startIndex = FindOurPositionOnPath(&close, true);
|
|
|
|
if (prevIndex)
|
|
*prevIndex = startIndex;
|
|
|
|
if (startIndex <= 0)
|
|
{
|
|
// went off the end of the path
|
|
// or next point in path is unwalkable (ie: jump-down)
|
|
// keep same point
|
|
return m_segmentIndex;
|
|
}
|
|
|
|
// if we are crouching, just follow the path exactly
|
|
if (m_improv->IsCrouching())
|
|
{
|
|
// we want to move to the immediately next point along the path from where we are now
|
|
int index = startIndex + 1;
|
|
if (index >= m_path->GetSegmentCount())
|
|
index = m_path->GetSegmentCount() - 1;
|
|
|
|
*point = (*m_path)[index]->pos;
|
|
|
|
// if we are very close to the next point in the path, skip ahead to the next one to avoid wiggling
|
|
// we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
|
|
const float closeEpsilon = 20.0f;
|
|
while ((*point - close).Make2D().IsLengthLessThan(closeEpsilon))
|
|
{
|
|
index++;
|
|
|
|
if (index >= m_path->GetSegmentCount())
|
|
{
|
|
index = m_path->GetSegmentCount() - 1;
|
|
break;
|
|
}
|
|
|
|
*point = (*m_path)[index]->pos;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
// make sure we use a node a minimum distance ahead of us, to avoid wiggling
|
|
while (startIndex < m_path->GetSegmentCount() - 1)
|
|
{
|
|
Vector pos = (*m_path)[startIndex + 1]->pos;
|
|
|
|
// we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
|
|
const float closeEpsilon = 20.0f;
|
|
if ((pos - close).Make2D().IsLengthLessThan(closeEpsilon))
|
|
{
|
|
startIndex++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we hit a ladder or jump area, must stop (dont use ladder behind us)
|
|
if (startIndex > m_segmentIndex && startIndex < m_path->GetSegmentCount() && ((*m_path)[startIndex]->ladder || ((*m_path)[startIndex]->area->GetAttributes() & NAV_JUMP)))
|
|
{
|
|
*point = (*m_path)[startIndex]->pos;
|
|
return startIndex;
|
|
}
|
|
|
|
// we need the point just *ahead* of us
|
|
if (++startIndex >= m_path->GetSegmentCount())
|
|
startIndex = m_path->GetSegmentCount() - 1;
|
|
|
|
// if we hit a ladder or jump area, must stop
|
|
if (startIndex < m_path->GetSegmentCount() && ((*m_path)[startIndex]->ladder || ((*m_path)[startIndex]->area->GetAttributes() & NAV_JUMP)))
|
|
{
|
|
*point = (*m_path)[startIndex]->pos;
|
|
return startIndex;
|
|
}
|
|
|
|
// note direction of path segment we are standing on
|
|
Vector initDir = (*m_path)[startIndex]->pos - (*m_path)[startIndex - 1]->pos;
|
|
initDir.NormalizeInPlace();
|
|
|
|
Vector feet = m_improv->GetFeet();
|
|
Vector eyes = m_improv->GetEyes();
|
|
float rangeSoFar = 0;
|
|
|
|
// this flag is true if our ahead point is visible
|
|
bool visible = true;
|
|
|
|
Vector prevDir = initDir;
|
|
|
|
// step along the path until we pass aheadRange
|
|
bool isCorner = false;
|
|
int i;
|
|
for (i = startIndex; i < m_path->GetSegmentCount(); i++)
|
|
{
|
|
Vector pos = (*m_path)[i]->pos;
|
|
Vector to = pos - (*m_path)[i - 1]->pos;
|
|
Vector dir = to.Normalize();
|
|
|
|
// don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc)
|
|
if (DotProduct(dir, initDir) < 0.0f) // -0.25f
|
|
{
|
|
i--;
|
|
break;
|
|
}
|
|
|
|
// if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc
|
|
if (DotProduct(dir, prevDir) < 0.5f)
|
|
{
|
|
isCorner = true;
|
|
i--;
|
|
break;
|
|
}
|
|
prevDir = dir;
|
|
|
|
// don't use points we cant see
|
|
Vector probe = pos + Vector(0, 0, HalfHumanHeight);
|
|
if (!IsWalkableTraceLineClear(eyes, probe, WALK_THRU_BREAKABLES))
|
|
{
|
|
// presumably, the previous point is visible, so we will interpolate
|
|
visible = false;
|
|
break;
|
|
}
|
|
|
|
// if we encounter a ladder or jump area, we must stop
|
|
if (i < m_path->GetSegmentCount() && ((*m_path)[i]->ladder || (*m_path)[i]->area->GetAttributes() & NAV_JUMP))
|
|
break;
|
|
|
|
// Check straight-line path from our current position to this position
|
|
// Test for un-jumpable height change, or unrecoverable fall
|
|
//if (!IsStraightLinePathWalkable(&pos))
|
|
//{
|
|
// i--;
|
|
// break;
|
|
//}
|
|
|
|
Vector along = (i == startIndex) ? (pos - feet) : (pos - (*m_path)[i - 1]->pos);
|
|
rangeSoFar += along.Length2D();
|
|
|
|
// stop if we have gone farther than aheadRange
|
|
if (rangeSoFar >= aheadRange)
|
|
break;
|
|
}
|
|
|
|
if (i < startIndex)
|
|
afterIndex = startIndex;
|
|
else if (i < m_path->GetSegmentCount())
|
|
afterIndex = i;
|
|
else
|
|
afterIndex = m_path->GetSegmentCount() - 1;
|
|
|
|
// compute point on the path at aheadRange
|
|
if (afterIndex == 0)
|
|
{
|
|
*point = (*m_path)[0]->pos;
|
|
}
|
|
else
|
|
{
|
|
// interpolate point along path segment
|
|
const Vector *afterPoint = &(*m_path)[afterIndex]->pos;
|
|
const Vector *beforePoint = &(*m_path)[afterIndex - 1]->pos;
|
|
|
|
Vector to = *afterPoint - *beforePoint;
|
|
float length = to.Length2D();
|
|
|
|
float t = 1.0f - ((rangeSoFar - aheadRange) / length);
|
|
|
|
if (t < 0.0f)
|
|
t = 0.0f;
|
|
else if (t > 1.0f)
|
|
t = 1.0f;
|
|
|
|
*point = *beforePoint + t * to;
|
|
|
|
// if afterPoint wasn't visible, slide point backwards towards beforePoint until it is
|
|
if (!visible)
|
|
{
|
|
const float sightStepSize = 25.0f;
|
|
float dt = sightStepSize / length;
|
|
|
|
Vector probe = *point + Vector(0, 0, HalfHumanHeight);
|
|
while (t > 0.0f && !IsWalkableTraceLineClear(eyes, probe, WALK_THRU_BREAKABLES))
|
|
{
|
|
t -= dt;
|
|
*point = *beforePoint + t * to;
|
|
}
|
|
|
|
if (t <= 0.0f)
|
|
*point = *beforePoint;
|
|
}
|
|
}
|
|
|
|
// if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle
|
|
if (!isCorner)
|
|
{
|
|
const float epsilon = 50.0f;
|
|
Vector2D toPoint;
|
|
Vector2D centroid(m_improv->GetCentroid().x, m_improv->GetCentroid().y);
|
|
|
|
toPoint.x = point->x - centroid.x;
|
|
toPoint.y = point->y - centroid.y;
|
|
|
|
if (DotProduct(toPoint, initDir.Make2D()) < 0.0f || toPoint.IsLengthLessThan(epsilon))
|
|
{
|
|
int i;
|
|
for (i = startIndex; i < m_path->GetSegmentCount(); i++)
|
|
{
|
|
toPoint.x = (*m_path)[i]->pos.x - centroid.x;
|
|
toPoint.y = (*m_path)[i]->pos.y - centroid.y;
|
|
if ((*m_path)[i]->ladder || ((*m_path)[i]->area->GetAttributes() & NAV_JUMP) || toPoint.IsLengthGreaterThan(epsilon))
|
|
{
|
|
*point = (*m_path)[i]->pos;
|
|
startIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == m_path->GetSegmentCount())
|
|
{
|
|
*point = m_path->GetEndpoint();
|
|
startIndex = m_path->GetSegmentCount() - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// m_pathIndex should always be the next point on the path, even if we're not moving directly towards it
|
|
if (startIndex < m_path->GetSegmentCount())
|
|
return startIndex;
|
|
|
|
return m_path->GetSegmentCount() - 1;
|
|
}
|
|
|
|
// Do reflex avoidance movements if our "feelers" are touched
|
|
// TODO: Parameterize feeler spacing
|
|
void CNavPathFollower::FeelerReflexAdjustment(Vector *goalPosition, float height)
|
|
{
|
|
// if we are in a "precise" area, do not do feeler adjustments
|
|
if (m_improv->GetLastKnownArea() && (m_improv->GetLastKnownArea()->GetAttributes() & NAV_PRECISE))
|
|
return;
|
|
|
|
Vector dir(BotCOS(m_improv->GetMoveAngle()), BotSIN(m_improv->GetMoveAngle()), 0.0f);
|
|
dir.z = 0.0f;
|
|
|
|
#ifndef PLAY_GAMEDLL
|
|
dir.NormalizeInPlace();
|
|
#else
|
|
// TODO: fix test demo
|
|
real_t flLen = dir.Length();
|
|
|
|
if (flLen > 0)
|
|
dir = dir * float(1 / flLen);
|
|
else
|
|
dir = Vector(0, 0, 0);
|
|
#endif
|
|
|
|
Vector lat(-dir.y, dir.x, 0.0f);
|
|
|
|
const float feelerOffset = (m_improv->IsCrouching()) ? 20.0f : 25.0f; // 15, 20
|
|
const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747)
|
|
const float feelerLengthWalk = 30.0f;
|
|
|
|
// if obstacle is lower than StepHeight, we'll walk right over it
|
|
const float feelerHeight = (height > 0.0f) ? height : StepHeight + 0.1f;
|
|
float feelerLength = (m_improv->IsRunning()) ? feelerLengthRun : feelerLengthWalk;
|
|
|
|
feelerLength = (m_improv->IsCrouching()) ? 20.0f : feelerLength;
|
|
|
|
// Feelers must follow floor slope
|
|
float ground;
|
|
Vector normal;
|
|
if (m_improv->GetSimpleGroundHeightWithFloor(&m_improv->GetEyes(), &ground, &normal) == false)
|
|
return;
|
|
|
|
// get forward vector along floor
|
|
dir = CrossProduct(lat, normal);
|
|
|
|
// correct the sideways vector
|
|
lat = CrossProduct(dir, normal);
|
|
|
|
Vector feet = m_improv->GetFeet();
|
|
feet.z += feelerHeight;
|
|
|
|
Vector from = feet + feelerOffset * lat;
|
|
Vector to = from + feelerLength * dir;
|
|
|
|
bool leftClear = IsWalkableTraceLineClear(from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES);
|
|
|
|
// draw debug beams
|
|
if (m_isDebug)
|
|
{
|
|
if (leftClear)
|
|
UTIL_DrawBeamPoints(from, to, 1, 0, 255, 0);
|
|
else
|
|
UTIL_DrawBeamPoints(from, to, 1, 255, 0, 0);
|
|
}
|
|
|
|
from = feet - feelerOffset * lat;
|
|
to = from + feelerLength * dir;
|
|
|
|
bool rightClear = IsWalkableTraceLineClear(from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES);
|
|
|
|
// draw debug beams
|
|
if (m_isDebug)
|
|
{
|
|
if (rightClear)
|
|
UTIL_DrawBeamPoints(from, to, 1, 0, 255, 0);
|
|
else
|
|
UTIL_DrawBeamPoints(from, to, 1, 255, 0, 0);
|
|
}
|
|
|
|
const real_t avoidRange = (m_improv->IsCrouching()) ? 150.0f : 300.0f;
|
|
|
|
if (!rightClear)
|
|
{
|
|
if (leftClear)
|
|
{
|
|
// right hit, left clear - veer left
|
|
*goalPosition = *goalPosition + avoidRange * lat;
|
|
//*goalPosition = m_improv->GetFeet() + avoidRange * lat;
|
|
//m_improv->StrafeLeft();
|
|
}
|
|
}
|
|
else if (!leftClear)
|
|
{
|
|
// right clear, left hit - veer right
|
|
*goalPosition = *goalPosition - avoidRange * lat;
|
|
//*goalPosition = m_improv->GetFeet() - avoidRange * lat;
|
|
//m_improv->StrafeRight();
|
|
}
|
|
}
|
|
|
|
// Reset the stuck-checker
|
|
CStuckMonitor::CStuckMonitor()
|
|
{
|
|
m_isStuck = false;
|
|
m_avgVelIndex = 0;
|
|
m_avgVelCount = 0;
|
|
}
|
|
|
|
// Reset the stuck-checker
|
|
void CStuckMonitor::Reset()
|
|
{
|
|
m_isStuck = false;
|
|
m_avgVelIndex = 0;
|
|
m_avgVelCount = 0;
|
|
}
|
|
|
|
// Test if the improv has become stuck
|
|
void CStuckMonitor::Update(CImprov *improv)
|
|
{
|
|
if (m_isStuck)
|
|
{
|
|
// improv is stuck - see if it has moved far enough to be considered unstuck
|
|
const float unstuckRange = 75.0f;
|
|
if ((improv->GetCentroid() - m_stuckSpot).IsLengthGreaterThan(unstuckRange))
|
|
{
|
|
// no longer stuck
|
|
Reset();
|
|
//PrintIfWatched( "UN-STUCK\n" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// check if improv has become stuck
|
|
|
|
// compute average velocity over a short period (for stuck check)
|
|
Vector vel = improv->GetCentroid() - m_lastCentroid;
|
|
|
|
// if we are jumping, ignore Z
|
|
//if (improv->IsJumping())
|
|
// vel.z = 0.0f;
|
|
|
|
// ignore Z unless we are on a ladder (which is only Z)
|
|
if (!improv->IsUsingLadder())
|
|
vel.z = 0.0f;
|
|
|
|
// cannot be Length2D, or will break ladder movement (they are only Z)
|
|
float moveDist = vel.Length();
|
|
|
|
float deltaT = gpGlobals->time - m_lastTime;
|
|
if (deltaT <= 0.0f)
|
|
return;
|
|
|
|
m_lastTime = gpGlobals->time;
|
|
|
|
// compute current velocity
|
|
m_avgVel[m_avgVelIndex++] = moveDist / deltaT;
|
|
|
|
if (m_avgVelIndex == MAX_VEL_SAMPLES)
|
|
m_avgVelIndex = 0;
|
|
|
|
if (m_avgVelCount < MAX_VEL_SAMPLES)
|
|
{
|
|
m_avgVelCount++;
|
|
}
|
|
else
|
|
{
|
|
// we have enough samples to know if we're stuck
|
|
float avgVel = 0.0f;
|
|
for (int t = 0; t < m_avgVelCount; t++)
|
|
avgVel += m_avgVel[t];
|
|
|
|
avgVel /= m_avgVelCount;
|
|
|
|
// cannot make this velocity too high, or actors will get "stuck" when going down ladders
|
|
float stuckVel = (improv->IsUsingLadder()) ? 10.0f : 20.0f;
|
|
|
|
if (avgVel < stuckVel)
|
|
{
|
|
// note when and where we initially become stuck
|
|
m_stuckTimer.Start();
|
|
m_stuckSpot = improv->GetCentroid();
|
|
m_isStuck = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// always need to track this
|
|
m_lastCentroid = improv->GetCentroid();
|
|
}
|