mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 22:03:56 -07:00
Unit Motion - combine Goal computation logic from MoveToPoint and MoveToTarget
MoveToPoint and MoveToTarget both compute a goal from a move request. They can be combined to reduce duplication and streamline the code. Differential Revision: https://code.wildfiregames.com/D1984 This was SVN commit r22450.
This commit is contained in:
parent
7d610d3412
commit
bbc2e84160
1 changed files with 131 additions and 216 deletions
|
|
@ -602,11 +602,6 @@ private:
|
|||
*/
|
||||
bool PathingUpdateNeeded(const CFixedVector2D& from);
|
||||
|
||||
/**
|
||||
* Update goal position if moving target
|
||||
*/
|
||||
void UpdateFinalGoal();
|
||||
|
||||
/**
|
||||
* Returns whether we are close enough to the target to assume it's a good enough
|
||||
* position to stop.
|
||||
|
|
@ -631,6 +626,12 @@ private:
|
|||
*/
|
||||
ControlGroupMovementObstructionFilter GetObstructionFilter(bool noTarget = false) const;
|
||||
|
||||
/**
|
||||
* Create a PathGoal from a move request.
|
||||
* @returns true if the goal was successfully created.
|
||||
*/
|
||||
bool ComputeGoal(PathGoal& out, const MoveRequest& moveRequest) const;
|
||||
|
||||
/**
|
||||
* Start moving to the given goal, from our current position 'from'.
|
||||
* Might go in a straight line immediately, or might start an asynchronous
|
||||
|
|
@ -722,7 +723,7 @@ void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
|
|||
return;
|
||||
}
|
||||
|
||||
UpdateFinalGoal();
|
||||
ComputeGoal(m_FinalGoal, m_MoveRequest);
|
||||
RequestLongPath(pos, m_FinalGoal);
|
||||
m_PathState = PATHSTATE_WAITING_REQUESTING_LONG;
|
||||
return;
|
||||
|
|
@ -952,7 +953,7 @@ bool CCmpUnitMotion::HandleObstructedMove()
|
|||
return true;
|
||||
}
|
||||
// Else, just entirely recompute
|
||||
UpdateFinalGoal();
|
||||
ComputeGoal(m_FinalGoal, m_MoveRequest);
|
||||
BeginPathing(pos, m_FinalGoal);
|
||||
|
||||
// potential TODO: We could switch the short-range pathfinder for something else entirely.
|
||||
|
|
@ -1096,22 +1097,6 @@ bool CCmpUnitMotion::PathingUpdateNeeded(const CFixedVector2D& from)
|
|||
return true;
|
||||
}
|
||||
|
||||
void CCmpUnitMotion::UpdateFinalGoal()
|
||||
{
|
||||
if (m_MoveRequest.m_Type != MoveRequest::ENTITY || m_MoveRequest.m_Type != MoveRequest::OFFSET)
|
||||
return;
|
||||
CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_MoveRequest.m_Entity);
|
||||
if (!cmpUnitMotion)
|
||||
return;
|
||||
if (IsFormationMember())
|
||||
return;
|
||||
CFixedVector2D targetPos;
|
||||
if (!ComputeTargetPosition(targetPos))
|
||||
return;
|
||||
m_FinalGoal.x = targetPos.X;
|
||||
m_FinalGoal.z = targetPos.Y;
|
||||
}
|
||||
|
||||
bool CCmpUnitMotion::CloseEnoughFromDestinationToStop(const CFixedVector2D& from) const
|
||||
{
|
||||
if (m_MoveRequest.m_Type != MoveRequest::POINT || m_FinalGoal.DistanceToPoint(from) > SHORT_PATH_GOAL_RADIUS)
|
||||
|
|
@ -1172,7 +1157,124 @@ ControlGroupMovementObstructionFilter CCmpUnitMotion::GetObstructionFilter(bool
|
|||
return ControlGroupMovementObstructionFilter(ShouldAvoidMovingUnits(), group);
|
||||
}
|
||||
|
||||
// The pathfinder cannot go to "rounded rectangles" goals, which are what happens with square targets and a non-null range.
|
||||
// Depending on what the best approximation is, we either pretend the target is a circle or a square.
|
||||
// One needs to be careful that the approximated geometry will be in the range.
|
||||
bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const
|
||||
{
|
||||
// Given a square, plus a target range we should reach, the shape at that distance
|
||||
// is a round-cornered square which we can approximate as either a circle or as a square.
|
||||
// Previously, we used the shape that minimized the worst-case error.
|
||||
// However that is unsage in some situations. So let's be less clever and
|
||||
// just check if our range is at least three times bigger than the circleradius
|
||||
return (range > circleRadius*3);
|
||||
}
|
||||
|
||||
bool CCmpUnitMotion::ComputeGoal(PathGoal& out, const MoveRequest& moveRequest) const
|
||||
{
|
||||
if (moveRequest.m_Type == MoveRequest::NONE)
|
||||
return false;
|
||||
|
||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
|
||||
if (!cmpPosition || !cmpPosition->IsInWorld())
|
||||
return false;
|
||||
|
||||
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||
|
||||
CFixedVector2D targetPosition;
|
||||
if (!ComputeTargetPosition(targetPosition))
|
||||
return false;
|
||||
|
||||
ICmpObstructionManager::ObstructionSquare targetObstruction;
|
||||
if (moveRequest.m_Type == MoveRequest::ENTITY)
|
||||
{
|
||||
CmpPtr<ICmpObstruction> cmpTargetObstruction(GetSimContext(), moveRequest.m_Entity);
|
||||
if (cmpTargetObstruction)
|
||||
cmpTargetObstruction->GetObstructionSquare(targetObstruction);
|
||||
}
|
||||
targetObstruction.x = targetPosition.X;
|
||||
targetObstruction.z = targetPosition.Y;
|
||||
|
||||
ICmpObstructionManager::ObstructionSquare obstruction;
|
||||
CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
|
||||
if (cmpObstruction)
|
||||
cmpObstruction->GetObstructionSquare(obstruction);
|
||||
else
|
||||
{
|
||||
obstruction.x = pos.X;
|
||||
obstruction.z = pos.Y;
|
||||
}
|
||||
|
||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
|
||||
ENSURE(cmpObstructionManager);
|
||||
|
||||
entity_pos_t distance = cmpObstructionManager->DistanceBetweenShapes(obstruction, targetObstruction);
|
||||
|
||||
out.x = targetObstruction.x;
|
||||
out.z = targetObstruction.z;
|
||||
out.hw = targetObstruction.hw;
|
||||
out.hh = targetObstruction.hh;
|
||||
out.u = targetObstruction.u;
|
||||
out.v = targetObstruction.v;
|
||||
|
||||
if (moveRequest.m_MinRange > fixed::Zero() || moveRequest.m_MaxRange > fixed::Zero() ||
|
||||
targetObstruction.hw > fixed::Zero())
|
||||
out.type = PathGoal::SQUARE;
|
||||
else
|
||||
{
|
||||
out.type = PathGoal::POINT;
|
||||
return true;
|
||||
}
|
||||
|
||||
CFixedVector2D halfSize(targetObstruction.hw, targetObstruction.hh);
|
||||
|
||||
if (distance < moveRequest.m_MinRange)
|
||||
{
|
||||
entity_pos_t circleRadius = halfSize.Length();
|
||||
|
||||
// Distance checks are nearest edge to nearest edge, so we need to account for our clearance
|
||||
// and we must make sure diagonals also fit so multiply by slightly more than sqrt(2)
|
||||
entity_pos_t goalDistance = moveRequest.m_MinRange + m_Clearance * 3 / 2;
|
||||
|
||||
if (ShouldTreatTargetAsCircle(moveRequest.m_MinRange, circleRadius))
|
||||
{
|
||||
out.type = PathGoal::INVERTED_CIRCLE;
|
||||
out.hw = circleRadius + goalDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.type = PathGoal::INVERTED_SQUARE;
|
||||
out.hw = targetObstruction.hw + goalDistance;
|
||||
out.hh = targetObstruction.hh + goalDistance;
|
||||
}
|
||||
}
|
||||
else if (moveRequest.m_MaxRange >= fixed::Zero() && distance > moveRequest.m_MaxRange)
|
||||
{
|
||||
entity_pos_t circleRadius = halfSize.Length();
|
||||
|
||||
if (ShouldTreatTargetAsCircle(moveRequest.m_MaxRange, circleRadius))
|
||||
{
|
||||
entity_pos_t goalDistance = moveRequest.m_MaxRange;
|
||||
|
||||
out.type = PathGoal::CIRCLE;
|
||||
out.hw = circleRadius + goalDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The target is large relative to our range, so treat it as a square and
|
||||
// get close enough that the diagonals come within range
|
||||
|
||||
entity_pos_t goalDistance = moveRequest.m_MaxRange * 2 / 3; // multiply by slightly less than 1/sqrt(2)
|
||||
|
||||
out.type = PathGoal::SQUARE;
|
||||
entity_pos_t delta = std::max(goalDistance, m_Clearance + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself
|
||||
out.hw = targetObstruction.hw + delta;
|
||||
out.hh = targetObstruction.hh + delta;
|
||||
}
|
||||
}
|
||||
// Do nothing in particular in case we are already in range.
|
||||
return true;
|
||||
}
|
||||
|
||||
void CCmpUnitMotion::BeginPathing(const CFixedVector2D& from, const PathGoal& goal)
|
||||
{
|
||||
|
|
@ -1270,68 +1372,15 @@ bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos
|
|||
if (!cmpPosition || !cmpPosition->IsInWorld())
|
||||
return false;
|
||||
|
||||
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||
|
||||
PathGoal goal;
|
||||
goal.x = x;
|
||||
goal.z = z;
|
||||
|
||||
if (minRange.IsZero() && maxRange.IsZero())
|
||||
{
|
||||
// Non-ranged movement:
|
||||
|
||||
// Head directly for the goal
|
||||
goal.type = PathGoal::POINT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ranged movement:
|
||||
|
||||
entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
|
||||
|
||||
if (distance < minRange)
|
||||
{
|
||||
// Too close to target - move outwards to a circle
|
||||
// that's slightly larger than the min range.
|
||||
goal.type = PathGoal::INVERTED_CIRCLE;
|
||||
|
||||
// Distance checks are nearest edge to nearest edge, so we need to account for our clearance
|
||||
// and we must make sure diagonals also fit so multiply by slightly more than sqrt(2)
|
||||
goal.hw = minRange + m_Clearance * 3 /2;
|
||||
}
|
||||
else if (maxRange >= entity_pos_t::Zero() && distance > maxRange)
|
||||
{
|
||||
// Too far from target - move inwards to a circle
|
||||
// that's slightly smaller than the max range
|
||||
goal.type = PathGoal::CIRCLE;
|
||||
goal.hw = maxRange;
|
||||
|
||||
// If maxRange was abnormally small,
|
||||
// collapse the circle into a point
|
||||
if (goal.hw <= entity_pos_t::Zero())
|
||||
goal.type = PathGoal::POINT;
|
||||
}
|
||||
}
|
||||
|
||||
m_MoveRequest = MoveRequest(CFixedVector2D(x, z), minRange, maxRange);
|
||||
m_FinalGoal = goal;
|
||||
ComputeGoal(m_FinalGoal, m_MoveRequest);
|
||||
m_Tries = 0;
|
||||
|
||||
BeginPathing(pos, goal);
|
||||
BeginPathing(cmpPosition->GetPosition2D(), m_FinalGoal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const
|
||||
{
|
||||
// Given a square, plus a target range we should reach, the shape at that distance
|
||||
// is a round-cornered square which we can approximate as either a circle or as a square.
|
||||
// Previously, we used the shape that minimized the worst-case error.
|
||||
// However that is unsage in some situations. So let's be less clever and
|
||||
// just check if our range is at least three times bigger than the circleradius
|
||||
return (range > circleRadius*3);
|
||||
}
|
||||
|
||||
bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
|
||||
{
|
||||
PROFILE("MoveToTargetRange");
|
||||
|
|
@ -1340,138 +1389,11 @@ bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange
|
|||
if (!cmpPosition || !cmpPosition->IsInWorld())
|
||||
return false;
|
||||
|
||||
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||
|
||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
|
||||
if (!cmpObstructionManager)
|
||||
return false;
|
||||
|
||||
bool hasObstruction = false;
|
||||
ICmpObstructionManager::ObstructionSquare obstruction;
|
||||
CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), target);
|
||||
if (cmpObstruction)
|
||||
hasObstruction = cmpObstruction->GetObstructionSquare(obstruction);
|
||||
|
||||
if (!hasObstruction)
|
||||
{
|
||||
// The target didn't have an obstruction or obstruction shape, so treat it as a point instead
|
||||
|
||||
CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
|
||||
if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
|
||||
return false;
|
||||
|
||||
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
|
||||
|
||||
return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're starting outside the maxRange, we need to move closer in.
|
||||
* If we're starting inside the minRange, we need to move further out.
|
||||
* These ranges are measured from the edge of this entity to the edge of the target;
|
||||
* we add the goal range onto the size of the target shape to get the goal shape.
|
||||
* (Then we extend it outwards/inwards by a little bit to be sure we'll end up
|
||||
* within the right range, in case of minor numerical inaccuracies.)
|
||||
*
|
||||
* There's a bit of a problem with large square targets:
|
||||
* the pathfinder only lets us move to goals that are squares, but the points an equal
|
||||
* distance from the target make a rounded square shape instead.
|
||||
*
|
||||
* When moving closer, we could shrink the goal radius to 1/sqrt(2) so the goal shape fits entirely
|
||||
* within the desired rounded square, but that gives an unfair advantage to attackers who approach
|
||||
* the target diagonally.
|
||||
*
|
||||
* If the target is small relative to the range (e.g. archers attacking anything),
|
||||
* then we cheat and pretend the target is actually a circle.
|
||||
* (TODO: that probably looks rubbish for things like walls?)
|
||||
*
|
||||
* If the target is large relative to the range (e.g. melee units attacking buildings),
|
||||
* then we multiply maxRange by approx 1/sqrt(2) to guarantee they'll always aim close enough.
|
||||
* (Those units should set minRange to 0 so they'll never be considered *too* close.)
|
||||
*/
|
||||
|
||||
CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
|
||||
PathGoal goal;
|
||||
goal.x = obstruction.x;
|
||||
goal.z = obstruction.z;
|
||||
|
||||
entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize, true);
|
||||
|
||||
// Compare with previous obstruction
|
||||
ICmpObstructionManager::ObstructionSquare previousObstruction;
|
||||
cmpObstruction->GetPreviousObstructionSquare(previousObstruction);
|
||||
entity_pos_t previousDistance = Geometry::DistanceToSquare(pos - CFixedVector2D(previousObstruction.x, previousObstruction.z), obstruction.u, obstruction.v, halfSize, true);
|
||||
|
||||
bool inside = distance.IsZero() && !Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize).IsZero();
|
||||
if ((distance < minRange && previousDistance < minRange) || inside)
|
||||
{
|
||||
// Too close to the square - need to move away
|
||||
|
||||
// Circumscribe the square
|
||||
entity_pos_t circleRadius = halfSize.Length();
|
||||
|
||||
// Distance checks are nearest edge to nearest edge, so we need to account for our clearance
|
||||
// and we must make sure diagonals also fit so multiply by slightly more than sqrt(2)
|
||||
entity_pos_t goalDistance = minRange + m_Clearance * 3 /2;
|
||||
|
||||
if (ShouldTreatTargetAsCircle(minRange, circleRadius))
|
||||
{
|
||||
// The target is small relative to our range, so pretend it's a circle
|
||||
goal.type = PathGoal::INVERTED_CIRCLE;
|
||||
goal.hw = circleRadius + goalDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
goal.type = PathGoal::INVERTED_SQUARE;
|
||||
goal.u = obstruction.u;
|
||||
goal.v = obstruction.v;
|
||||
goal.hw = obstruction.hw + goalDistance;
|
||||
goal.hh = obstruction.hh + goalDistance;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We might need to move closer:
|
||||
|
||||
// Circumscribe the square
|
||||
entity_pos_t circleRadius = halfSize.Length();
|
||||
|
||||
if (ShouldTreatTargetAsCircle(maxRange, circleRadius))
|
||||
{
|
||||
// The target is small relative to our range, so pretend it's a circle
|
||||
|
||||
// Note that the distance to the circle will always be less than
|
||||
// the distance to the square, so the previous "distance < maxRange"
|
||||
// check is still valid (though not sufficient)
|
||||
entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius;
|
||||
entity_pos_t previousCircleDistance = (pos - CFixedVector2D(previousObstruction.x, previousObstruction.z)).Length() - circleRadius;
|
||||
|
||||
entity_pos_t goalDistance = maxRange;
|
||||
|
||||
goal.type = PathGoal::CIRCLE;
|
||||
goal.hw = circleRadius + goalDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The target is large relative to our range, so treat it as a square and
|
||||
// get close enough that the diagonals come within range
|
||||
|
||||
entity_pos_t goalDistance = maxRange * 2 / 3; // multiply by slightly less than 1/sqrt(2)
|
||||
|
||||
goal.type = PathGoal::SQUARE;
|
||||
goal.u = obstruction.u;
|
||||
goal.v = obstruction.v;
|
||||
entity_pos_t delta = std::max(goalDistance, m_Clearance + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself
|
||||
goal.hw = obstruction.hw + delta;
|
||||
goal.hh = obstruction.hh + delta;
|
||||
}
|
||||
}
|
||||
|
||||
m_MoveRequest = MoveRequest(target, minRange, maxRange);
|
||||
m_FinalGoal = goal;
|
||||
ComputeGoal(m_FinalGoal, m_MoveRequest);
|
||||
m_Tries = 0;
|
||||
|
||||
BeginPathing(pos, goal);
|
||||
BeginPathing(cmpPosition->GetPosition2D(), m_FinalGoal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1482,18 +1404,11 @@ void CCmpUnitMotion::MoveToFormationOffset(entity_id_t target, entity_pos_t x, e
|
|||
if (!cmpPosition || !cmpPosition->IsInWorld())
|
||||
return;
|
||||
|
||||
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||
|
||||
PathGoal goal;
|
||||
goal.type = PathGoal::POINT;
|
||||
goal.x = pos.X;
|
||||
goal.z = pos.Y;
|
||||
|
||||
m_MoveRequest = MoveRequest(target, CFixedVector2D(x, z));
|
||||
m_FinalGoal = goal;
|
||||
ComputeGoal(m_FinalGoal, m_MoveRequest);
|
||||
m_Tries = 0;
|
||||
|
||||
BeginPathing(pos, goal);
|
||||
BeginPathing(cmpPosition->GetPosition2D(), m_FinalGoal);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue