Use an improved turn radius determination
This commit is contained in:
@@ -29,6 +29,15 @@
|
||||
#include "tracks/battle_graph.hpp"
|
||||
#include "utils/log.hpp"
|
||||
|
||||
int ArenaAI::m_test_node_for_banana = BattleGraph::UNKNOWN_POLY;
|
||||
|
||||
bool isNodeWithBanana(const std::pair<const Item*, int>& item_pair)
|
||||
{
|
||||
return item_pair.second == ArenaAI::m_test_node_for_banana &&
|
||||
item_pair.first->getType() == Item::ITEM_BANANA &&
|
||||
!item_pair.first->wasCollected();
|
||||
}
|
||||
|
||||
ArenaAI::ArenaAI(AbstractKart *kart)
|
||||
: AIBaseController(kart)
|
||||
{
|
||||
@@ -52,14 +61,16 @@ void ArenaAI::reset()
|
||||
m_cur_kart_pos_data = {0};
|
||||
m_is_stuck = false;
|
||||
m_is_uturn = false;
|
||||
m_avoid_eating_banana = false;
|
||||
m_target_point = Vec3(0, 0, 0);
|
||||
m_time_since_last_shot = 0.0f;
|
||||
m_time_since_driving = 0.0f;
|
||||
m_time_since_reversing = 0.0f;
|
||||
m_time_since_uturn = 0.0f;
|
||||
m_turn_radius = 0.0f;
|
||||
m_turn_angle = 0.0f;
|
||||
m_on_node.clear();
|
||||
m_aiming_points.clear();
|
||||
m_aiming_nodes.clear();
|
||||
|
||||
m_cur_difficulty = race_manager->getDifficulty();
|
||||
AIBaseController::reset();
|
||||
@@ -96,11 +107,10 @@ void ArenaAI::update(float dt)
|
||||
findClosestKart(true);
|
||||
findTarget();
|
||||
handleArenaItems(dt);
|
||||
handleArenaBanana();
|
||||
|
||||
if (m_kart->getSpeed() > 15.0f && m_cur_kart_pos_data.angle < 0.2f)
|
||||
if (m_kart->getSpeed() > 15.0f && m_turn_angle < 20)
|
||||
{
|
||||
// Only use nitro when target is straight
|
||||
// Only use nitro when turn angle is big (180 - angle)
|
||||
m_controls->m_nitro = true;
|
||||
}
|
||||
|
||||
@@ -121,44 +131,57 @@ void ArenaAI::update(float dt)
|
||||
} // update
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool ArenaAI::getAimingPoints()
|
||||
bool ArenaAI::updateAimingPosition()
|
||||
{
|
||||
// Notice: we use the point ahead of kart to determine next node,
|
||||
// to compensate the time difference between steering
|
||||
m_current_forward_point =
|
||||
m_kart->getTrans()(Vec3(0, 0, m_kart->getKartLength()));
|
||||
m_current_forward_node = BattleGraph::get()->pointToNode
|
||||
(m_current_forward_node, m_current_forward_point,
|
||||
false/*ignore_vertical*/);
|
||||
|
||||
if (m_current_forward_node == BattleGraph::UNKNOWN_POLY ||
|
||||
m_current_forward_node == m_target_node)
|
||||
m_current_forward_node = getCurrentNode();
|
||||
// Use current node if forward node is unknown, or near the target
|
||||
const int forward = (m_current_forward_node == BattleGraph::UNKNOWN_POLY ||
|
||||
m_current_forward_node == m_target_node ||
|
||||
getCurrentNode() == m_target_node ? getCurrentNode() :
|
||||
m_current_forward_node);
|
||||
|
||||
int first = m_current_forward_node;
|
||||
int last = m_target_node;
|
||||
|
||||
if (first == BattleGraph::UNKNOWN_POLY ||
|
||||
last == BattleGraph::UNKNOWN_POLY)
|
||||
if (forward == BattleGraph::UNKNOWN_POLY ||
|
||||
m_target_node == BattleGraph::UNKNOWN_POLY)
|
||||
return false;
|
||||
|
||||
if (m_current_forward_node == m_target_node)
|
||||
if (forward == m_target_node)
|
||||
{
|
||||
m_aiming_points.push_back(BattleGraph::get()
|
||||
->getPolyOfNode(first).getCenter());
|
||||
->getPolyOfNode(forward).getCenter());
|
||||
m_aiming_points.push_back(m_target_point);
|
||||
|
||||
m_aiming_nodes.insert(forward);
|
||||
m_aiming_nodes.insert(getCurrentNode());
|
||||
return true;
|
||||
}
|
||||
|
||||
m_aiming_points.push_back(BattleGraph::get()
|
||||
->getPolyOfNode(first).getCenter());
|
||||
const int next_node = BattleGraph::get()
|
||||
->getNextShortestPathPoly(first, last);
|
||||
assert(next_node != BattleGraph::UNKNOWN_POLY);
|
||||
->getNextShortestPathPoly(forward, m_target_node);
|
||||
if (next_node == BattleGraph::UNKNOWN_POLY)
|
||||
{
|
||||
Log::error("ArenaAI", "Next node is unknown, did you forget to link"
|
||||
"adjacent face in navmesh?");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_aiming_points.push_back(BattleGraph::get()
|
||||
->getPolyOfNode(forward).getCenter());
|
||||
m_aiming_points.push_back(BattleGraph::get()
|
||||
->getPolyOfNode(next_node).getCenter());
|
||||
|
||||
m_aiming_nodes.insert(forward);
|
||||
m_aiming_nodes.insert(next_node);
|
||||
m_aiming_nodes.insert(getCurrentNode());
|
||||
|
||||
return true;
|
||||
} // getAimingPoints
|
||||
} // updateAimingPosition
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** This function sets the steering.
|
||||
@@ -169,10 +192,14 @@ void ArenaAI::handleArenaSteering(const float dt)
|
||||
const int current_node = getCurrentNode();
|
||||
|
||||
if (current_node == BattleGraph::UNKNOWN_POLY ||
|
||||
m_target_node == BattleGraph::UNKNOWN_POLY) return;
|
||||
m_target_node == BattleGraph::UNKNOWN_POLY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_aiming_points.clear();
|
||||
const bool found_points = getAimingPoints();
|
||||
m_aiming_nodes.clear();
|
||||
const bool found_position = updateAimingPosition();
|
||||
if (ignorePathFinding())
|
||||
{
|
||||
// Steer directly
|
||||
@@ -192,8 +219,12 @@ void ArenaAI::handleArenaSteering(const float dt)
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (found_points)
|
||||
else if (found_position)
|
||||
{
|
||||
updateBananaLocation();
|
||||
assert(m_aiming_points.size() == 2);
|
||||
updateTurnRadius(m_kart->getXYZ(), m_aiming_points[0],
|
||||
m_aiming_points[1]);
|
||||
m_target_point = m_aiming_points[1];
|
||||
checkPosition(m_target_point, &m_cur_kart_pos_data);
|
||||
#ifdef AI_DEBUG
|
||||
@@ -283,7 +314,7 @@ void ArenaAI::handleArenaUTurn(const float dt)
|
||||
|
||||
if (fabsf(m_kart->getSpeed()) >
|
||||
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
|
||||
&& m_kart->getSpeed() < 0) // Try to emulate reverse like human players
|
||||
&& m_kart->getSpeed() < 0) // Try to emulate reverse like human players
|
||||
m_controls->m_accel = -0.06f;
|
||||
else
|
||||
m_controls->m_accel = -5.0f;
|
||||
@@ -334,43 +365,49 @@ bool ArenaAI::handleArenaUnstuck(const float dt)
|
||||
} // handleArenaUnstuck
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void ArenaAI::handleArenaBanana()
|
||||
void ArenaAI::updateBananaLocation()
|
||||
{
|
||||
m_avoid_eating_banana = false;
|
||||
|
||||
if (m_is_uturn) return;
|
||||
|
||||
const std::vector< std::pair<const Item*, int> >& item_list =
|
||||
std::vector<std::pair<const Item*, int>>& item_list =
|
||||
BattleGraph::get()->getItemList();
|
||||
const unsigned int items_count = item_list.size();
|
||||
for (unsigned int i = 0; i < items_count; ++i)
|
||||
|
||||
std::set<int>::iterator node = m_aiming_nodes.begin();
|
||||
while (node != m_aiming_nodes.end())
|
||||
{
|
||||
const Item* item = item_list[i].first;
|
||||
if (item->getType() == Item::ITEM_BANANA && !item->wasCollected())
|
||||
m_test_node_for_banana = *node;
|
||||
std::vector<std::pair<const Item*, int>>::iterator it =
|
||||
std::find_if(item_list.begin(), item_list.end(), isNodeWithBanana);
|
||||
|
||||
if (it != item_list.end())
|
||||
{
|
||||
Vec3 banana_lc;
|
||||
posData banana_pos = {0};
|
||||
Vec3 banana_lc(0, 0, 0);
|
||||
checkPosition(item->getXYZ(), &banana_pos, &banana_lc);
|
||||
if (banana_pos.angle < 0.2f && banana_pos.distance < 7.5f &&
|
||||
!banana_pos.behind)
|
||||
checkPosition(it->first->getXYZ(), &banana_pos, &banana_lc,
|
||||
true/*use_front_xyz*/);
|
||||
|
||||
// If satisfy the below condition, AI should not eat banana
|
||||
if (banana_pos.behind || banana_pos.angle > 0.3f)
|
||||
{
|
||||
// Check whether it's straight ahead towards a banana
|
||||
// If so, adjust target point
|
||||
banana_lc = (banana_pos.lhs ? banana_lc + Vec3 (2, 0, 0) :
|
||||
banana_lc - Vec3 (2, 0, 0));
|
||||
m_target_point = m_kart->getTrans()(banana_lc);
|
||||
m_avoid_eating_banana = true;
|
||||
// Handle one banana only
|
||||
break;
|
||||
node++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const float dist = banana_lc.length() * 2;
|
||||
banana_lc = (banana_lc.x() < 0 ? banana_lc + Vec3(dist, 0, 0) :
|
||||
banana_lc - Vec3(dist, 0, 0));
|
||||
// If the node AI will pass has a banana, adjust the aim position
|
||||
m_aiming_points[1] = m_kart->getTrans()(banana_lc);
|
||||
|
||||
// Handle one banana only
|
||||
return;
|
||||
}
|
||||
node++;
|
||||
}
|
||||
|
||||
} // handleArenaBanana
|
||||
} // updateBananaLocation
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** This function handles braking. It calls determineTurnRadius() to find out
|
||||
* the curve radius. Depending on the turn radius, it finds out the maximum
|
||||
/** This function handles braking. It used the turn radius found by
|
||||
* updateTurnRadius(). Depending on the turn radius, it finds out the maximum
|
||||
* speed. If the current speed is greater than the max speed and a set minimum
|
||||
* speed, brakes are applied.
|
||||
*/
|
||||
@@ -395,14 +432,11 @@ void ArenaAI::handleArenaBraking()
|
||||
|
||||
if (m_aiming_points.empty()) return;
|
||||
|
||||
float current_curve_radius = determineTurnRadius(m_kart->getXYZ(),
|
||||
m_aiming_points[0], m_aiming_points[1]);
|
||||
const float max_turn_speed = m_kart->getSpeedForTurnRadius(m_turn_radius);
|
||||
|
||||
float max_turn_speed = m_kart->getSpeedForTurnRadius(current_curve_radius);
|
||||
|
||||
if (m_kart->getSpeed() > max_turn_speed &&
|
||||
m_kart->getSpeed() > MIN_SPEED &&
|
||||
fabsf(m_controls->m_steer) > 0.3f)
|
||||
if (m_kart->getSpeed() > 1.25f * max_turn_speed &&
|
||||
fabsf(m_controls->m_steer) > 0.95f &&
|
||||
m_kart->getSpeed() > MIN_SPEED)
|
||||
{
|
||||
m_controls->m_brake = true;
|
||||
}
|
||||
@@ -410,52 +444,53 @@ void ArenaAI::handleArenaBraking()
|
||||
} // handleArenaBraking
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** The turn radius is determined by fitting a parabola to 3 points: current
|
||||
* location of AI, first corner and the second corner. Once the constants are
|
||||
* computed, a formula is used to find the radius of curvature at the vertex
|
||||
* of the parabola.
|
||||
*/
|
||||
float ArenaAI::determineTurnRadius(const Vec3& p1, const Vec3& p2,
|
||||
const Vec3& p3)
|
||||
void ArenaAI::updateTurnRadius(const Vec3& p1, const Vec3& p2,
|
||||
const Vec3& p3)
|
||||
{
|
||||
// The parabola function is as following: y=ax2+bx+c
|
||||
// No need to calculate c as after differentiating c will be zero
|
||||
const float eps = 0.01f;
|
||||
const float denominator = (p1.x() - p2.x()) * (p1.x() - p3.x()) *
|
||||
(p2.x() - p3.x());
|
||||
// First use cosine formula to find out the angle made by the distance
|
||||
// between kart (point one) to point two and point two between point three
|
||||
const float a = (p1 - p2).length();
|
||||
const float b = (p2 - p3).length();
|
||||
const float c = (p1 - p3).length();
|
||||
const float angle = 180 - findAngleFrom3Edges(a, b, c);
|
||||
|
||||
// Avoid nan, this will happen if three values of coordinates x are too
|
||||
// close together, ie a straight line, return a large radius
|
||||
// so no braking is needed
|
||||
if (fabsf(denominator) < eps) return 40.0f;
|
||||
// Only calculate radius if not almost straight line
|
||||
if (angle > 1 && angle < 179)
|
||||
{
|
||||
// Now find out the arc length ratio it has from the angle, so if the
|
||||
// angle is 90 degree, it can be deduced by (pi / 2) (90 degree)
|
||||
// over 2 * pi, which is 1 over 4. Notice: it is inversely proportional
|
||||
// to the ratio, so the higher the angle (non-hard turn) the calculated
|
||||
// ratio should be smaller (ie less part of the total circumference)
|
||||
// Then we do 180 - angle from above, which is c (distance from kart to
|
||||
// aiming point) = around 1 / 4 of the total circumference,
|
||||
// then the turn radius = c / (1 / 4) / pi / 2
|
||||
m_turn_radius = (a + b) / ((angle * DEGREE_TO_RAD) / (2 * M_PI)) /
|
||||
M_PI / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return large radius so no braking is needed otherwise
|
||||
m_turn_radius = 45.0f;
|
||||
}
|
||||
m_turn_angle = angle;
|
||||
|
||||
const float a = (p3.x() * (p2.z() - p1.z()) +
|
||||
p2.x() * (p1.z() - p3.z()) +
|
||||
p1.x() * (p3.z() - p2.z())) / denominator;
|
||||
} // updateTurnRadius
|
||||
|
||||
// Should not happen, otherwise y=c which is a straight line
|
||||
if (fabsf(a) < eps) return 40.0f;
|
||||
//-----------------------------------------------------------------------------
|
||||
float ArenaAI::findAngleFrom3Edges(float a, float b, float c)
|
||||
{
|
||||
// Cosine forumla : c2 = a2 + b2 - 2ab cos C
|
||||
float test_value = ((c * c) - (a * a) - (b * b)) / (-(2 * a * b));
|
||||
// Prevent error
|
||||
if (test_value < -1)
|
||||
test_value = -1;
|
||||
else if (test_value > 1)
|
||||
test_value = 1;
|
||||
|
||||
const float b = (p3.x() * p3.x() * (p1.z() - p2.z()) +
|
||||
p2.x() * p2.x() * (p3.z() - p1.z()) +
|
||||
p1.x() * p1.x() * (p2.z() - p3.z())) / denominator;
|
||||
return acosf(test_value) * RAD_TO_DEGREE;
|
||||
|
||||
// Vertex: -b / 2a
|
||||
const float vertex_x = -b / (2 * a);
|
||||
|
||||
// Differentiate the function, so y=ax2+bx+c will become y=2ax+b for dy_dx,
|
||||
// y=2a for d2y_dx2
|
||||
// Use the vertex of the parabola as x
|
||||
const float dy_dx = 2 * a * vertex_x + b;
|
||||
const float d2y_dx2 = 2 * a;
|
||||
|
||||
// Calculate the radius of curvature at current location of AI
|
||||
const float radius = pow(1 + pow(dy_dx, 2), 1.5f) / fabsf(d2y_dx2);
|
||||
assert(!std::isnan(radius));
|
||||
|
||||
// Avoid returning too large radius
|
||||
return (radius > 40.0f ? 40.0f : radius);
|
||||
} // determineTurnRadius
|
||||
} // findAngleFrom3Edges
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void ArenaAI::handleArenaItems(const float dt)
|
||||
|
||||
@@ -64,13 +64,8 @@ protected:
|
||||
/** The target point. */
|
||||
Vec3 m_target_point;
|
||||
|
||||
int m_current_forward_node;
|
||||
Vec3 m_current_forward_point;
|
||||
|
||||
/** For ignorePathFinding() to work */
|
||||
bool m_avoid_eating_banana;
|
||||
|
||||
void collectItemInArena(Vec3*, int*) const;
|
||||
void collectItemInArena(Vec3*, int*) const;
|
||||
float findAngleFrom3Edges(float a, float b, float c);
|
||||
private:
|
||||
/** Used by handleArenaUTurn, it tells whether to do left or right
|
||||
* turning when steering is overridden. */
|
||||
@@ -90,8 +85,6 @@ private:
|
||||
* stuck by determine the size of this set. */
|
||||
std::set<int> m_on_node;
|
||||
|
||||
std::vector<Vec3> m_aiming_points;
|
||||
|
||||
/** Time an item has been collected and not used. */
|
||||
float m_time_since_last_shot;
|
||||
|
||||
@@ -104,25 +97,35 @@ private:
|
||||
/** This is a timer that counts down when the kart is doing u-turn. */
|
||||
float m_time_since_uturn;
|
||||
|
||||
float m_turn_radius;
|
||||
float m_turn_angle;
|
||||
|
||||
Vec3 m_current_forward_point;
|
||||
int m_current_forward_node;
|
||||
|
||||
std::set<int> m_aiming_nodes;
|
||||
std::vector<Vec3> m_aiming_points;
|
||||
|
||||
void checkIfStuck(const float dt);
|
||||
float determineTurnRadius(const Vec3& p1, const Vec3& p2,
|
||||
const Vec3& p3);
|
||||
void handleArenaAcceleration(const float dt);
|
||||
void handleArenaBanana();
|
||||
void handleArenaBraking();
|
||||
void handleArenaItems(const float dt);
|
||||
void handleArenaSteering(const float dt);
|
||||
void handleArenaUTurn(const float dt);
|
||||
bool handleArenaUnstuck(const float dt);
|
||||
bool getAimingPoints();
|
||||
bool updateAimingPosition();
|
||||
void updateBananaLocation();
|
||||
void updateTurnRadius(const Vec3& p1, const Vec3& p2,
|
||||
const Vec3& p3);
|
||||
virtual int getCurrentNode() const = 0;
|
||||
virtual bool isWaiting() const = 0;
|
||||
virtual void resetAfterStop() {};
|
||||
virtual void findClosestKart(bool use_difficulty) = 0;
|
||||
virtual void findTarget() = 0;
|
||||
virtual bool forceBraking() { return false; }
|
||||
virtual bool ignorePathFinding() { return m_avoid_eating_banana; }
|
||||
virtual bool forceBraking() { return false; }
|
||||
virtual bool ignorePathFinding() { return false; }
|
||||
public:
|
||||
static int m_test_node_for_banana;
|
||||
ArenaAI(AbstractKart *kart);
|
||||
virtual ~ArenaAI() {};
|
||||
virtual void update (float delta);
|
||||
|
||||
@@ -212,6 +212,7 @@ Vec3 SoccerAI::determineBallAimingPosition()
|
||||
}
|
||||
#endif
|
||||
|
||||
m_chasing_ball = true;
|
||||
const Vec3& ball_aim_pos = m_world->getBallAimPosition(m_opp_team);
|
||||
const Vec3& orig_pos = m_world->getBallPosition();
|
||||
|
||||
@@ -260,7 +261,6 @@ Vec3 SoccerAI::determineBallAimingPosition()
|
||||
}
|
||||
}
|
||||
|
||||
m_chasing_ball = true;
|
||||
// Check if reached aim point, which is behind aiming position and
|
||||
// in front of the ball, if so use another aiming method
|
||||
if (aim_lc.z() < 0 && ball_lc.z() > 0)
|
||||
@@ -289,21 +289,6 @@ Vec3 SoccerAI::determineBallAimingPosition()
|
||||
|
||||
} // determineBallAimingPosition
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
float SoccerAI::findAngleFrom3Edges(float a, float b, float c)
|
||||
{
|
||||
// Cosine forumla : c2 = a2 + b2 - 2ab cos C
|
||||
float test_value = (c * c) - (a * a) - (b * b) / (-(2 * a * b));
|
||||
// Prevent error
|
||||
if (test_value < -1)
|
||||
test_value = -1;
|
||||
else if (test_value > 1)
|
||||
test_value = 1;
|
||||
|
||||
return acosf(test_value) * RAD_TO_DEGREE;
|
||||
|
||||
} // find3PointsAngle
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool SoccerAI::isOvertakable(const Vec3& ball_lc)
|
||||
{
|
||||
|
||||
@@ -59,7 +59,6 @@ private:
|
||||
bool determineOvertakePosition(const Vec3& ball_lc, const Vec3& aim_lc,
|
||||
Vec3* overtake_lc);
|
||||
float rotateSlope(float old_slope, bool rotate_up);
|
||||
float findAngleFrom3Edges(float a, float b, float c);
|
||||
|
||||
virtual void findClosestKart(bool use_difficulty);
|
||||
virtual void findTarget();
|
||||
@@ -69,10 +68,7 @@ private:
|
||||
virtual bool canSkid(float steer_fraction) { return false; }
|
||||
virtual bool forceBraking() OVERRIDE { return m_force_brake; }
|
||||
virtual bool ignorePathFinding() OVERRIDE
|
||||
{
|
||||
return m_avoid_eating_banana || m_overtake_ball || m_chasing_ball;
|
||||
}
|
||||
|
||||
{ return m_chasing_ball; }
|
||||
public:
|
||||
SoccerAI(AbstractKart *kart);
|
||||
~SoccerAI();
|
||||
|
||||
@@ -74,12 +74,10 @@ void BattleGraph::buildGraph(NavMesh* navmesh)
|
||||
std::vector<int> adjacents = navmesh->getAdjacentPolys(i);
|
||||
for(unsigned int j=0; j<adjacents.size(); j++)
|
||||
{
|
||||
Vec3 adjacentPolyCenter = navmesh->getCenterOfPoly(adjacents[j]);
|
||||
float distance = Vec3(adjacentPolyCenter - currentPoly.getCenter()).length_2d();
|
||||
|
||||
Vec3 diff = navmesh->getCenterOfPoly(adjacents[j]) - currentPoly.getCenter();
|
||||
float distance = diff.length();
|
||||
m_distance_matrix[i][adjacents[j]] = distance;
|
||||
//m_distance_matrix[adjacents[j]][i] = distance;
|
||||
|
||||
}
|
||||
m_distance_matrix[i][i] = 0.0f;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ public:
|
||||
* which is the next node on the path from i to j (undirected graph) */
|
||||
const int getNextShortestPathPoly(int i, int j) const;
|
||||
|
||||
const std::vector < std::pair<const Item*, int> >& getItemList()
|
||||
std::vector<std::pair<const Item*, int>>& getItemList()
|
||||
{ return m_items_on_graph; }
|
||||
// ------------------------------------------------------------------------
|
||||
void insertItems(Item* item, int polygon)
|
||||
|
||||
@@ -74,7 +74,7 @@ bool NavPoly::pointInPoly(const Vec3& p, bool ignore_vertical) const
|
||||
|
||||
// Check for vertical distance too
|
||||
const float dist = p.getY() - m_center.getY();
|
||||
if (fabsf(dist) > 1.0f )
|
||||
if (fabsf(dist) > 1.0f)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user