forked from LeenkxTeam/LNXSDK
Update Files
This commit is contained in:
36
lib/haxerecast/recastnavigation/DetourCrowd/CMakeLists.txt
Normal file
36
lib/haxerecast/recastnavigation/DetourCrowd/CMakeLists.txt
Normal file
@ -0,0 +1,36 @@
|
||||
file(GLOB SOURCES Source/*.cpp)
|
||||
add_library(DetourCrowd ${SOURCES})
|
||||
|
||||
add_library(RecastNavigation::DetourCrowd ALIAS DetourCrowd)
|
||||
set_target_properties(DetourCrowd PROPERTIES DEBUG_POSTFIX -d)
|
||||
|
||||
set(DetourCrowd_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Include")
|
||||
|
||||
target_include_directories(DetourCrowd PUBLIC
|
||||
"$<BUILD_INTERFACE:${DetourCrowd_INCLUDE_DIR}>"
|
||||
)
|
||||
|
||||
target_link_libraries(DetourCrowd
|
||||
Detour
|
||||
)
|
||||
|
||||
set_target_properties(DetourCrowd PROPERTIES
|
||||
SOVERSION ${SOVERSION}
|
||||
VERSION ${LIB_VERSION}
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY .
|
||||
COMPILE_PDB_NAME "DetourCrowd-d"
|
||||
)
|
||||
|
||||
install(TARGETS DetourCrowd
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
COMPONENT library
|
||||
)
|
||||
|
||||
file(GLOB INCLUDES Include/*.h)
|
||||
install(FILES ${INCLUDES} DESTINATION
|
||||
${CMAKE_INSTALL_INCLUDEDIR}/recastnavigation)
|
||||
if(MSVC)
|
||||
install(FILES "$<TARGET_FILE_DIR:DetourCrowd>/DetourCrowd-d.pdb" CONFIGURATIONS "Debug" DESTINATION "lib")
|
||||
endif()
|
@ -0,0 +1,460 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#ifndef DETOURCROWD_H
|
||||
#define DETOURCROWD_H
|
||||
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourObstacleAvoidance.h"
|
||||
#include "DetourLocalBoundary.h"
|
||||
#include "DetourPathCorridor.h"
|
||||
#include "DetourProximityGrid.h"
|
||||
#include "DetourPathQueue.h"
|
||||
|
||||
/// The maximum number of neighbors that a crowd agent can take into account
|
||||
/// for steering decisions.
|
||||
/// @ingroup crowd
|
||||
static const int DT_CROWDAGENT_MAX_NEIGHBOURS = 6;
|
||||
|
||||
/// The maximum number of corners a crowd agent will look ahead in the path.
|
||||
/// This value is used for sizing the crowd agent corner buffers.
|
||||
/// Due to the behavior of the crowd manager, the actual number of useful
|
||||
/// corners will be one less than this number.
|
||||
/// @ingroup crowd
|
||||
static const int DT_CROWDAGENT_MAX_CORNERS = 4;
|
||||
|
||||
/// The maximum number of crowd avoidance configurations supported by the
|
||||
/// crowd manager.
|
||||
/// @ingroup crowd
|
||||
/// @see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(), dtCrowd::getObstacleAvoidanceParams(),
|
||||
/// dtCrowdAgentParams::obstacleAvoidanceType
|
||||
static const int DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS = 8;
|
||||
|
||||
/// The maximum number of query filter types supported by the crowd manager.
|
||||
/// @ingroup crowd
|
||||
/// @see dtQueryFilter, dtCrowd::getFilter() dtCrowd::getEditableFilter(),
|
||||
/// dtCrowdAgentParams::queryFilterType
|
||||
static const int DT_CROWD_MAX_QUERY_FILTER_TYPE = 16;
|
||||
|
||||
/// Provides neighbor data for agents managed by the crowd.
|
||||
/// @ingroup crowd
|
||||
/// @see dtCrowdAgent::neis, dtCrowd
|
||||
struct dtCrowdNeighbour
|
||||
{
|
||||
int idx; ///< The index of the neighbor in the crowd.
|
||||
float dist; ///< The distance between the current agent and the neighbor.
|
||||
};
|
||||
|
||||
/// The type of navigation mesh polygon the agent is currently traversing.
|
||||
/// @ingroup crowd
|
||||
enum CrowdAgentState
|
||||
{
|
||||
DT_CROWDAGENT_STATE_INVALID, ///< The agent is not in a valid state.
|
||||
DT_CROWDAGENT_STATE_WALKING, ///< The agent is traversing a normal navigation mesh polygon.
|
||||
DT_CROWDAGENT_STATE_OFFMESH, ///< The agent is traversing an off-mesh connection.
|
||||
};
|
||||
|
||||
/// Configuration parameters for a crowd agent.
|
||||
/// @ingroup crowd
|
||||
struct dtCrowdAgentParams
|
||||
{
|
||||
float radius; ///< Agent radius. [Limit: >= 0]
|
||||
float height; ///< Agent height. [Limit: > 0]
|
||||
float maxAcceleration; ///< Maximum allowed acceleration. [Limit: >= 0]
|
||||
float maxSpeed; ///< Maximum allowed speed. [Limit: >= 0]
|
||||
|
||||
/// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
|
||||
float collisionQueryRange;
|
||||
|
||||
float pathOptimizationRange; ///< The path visibility optimization range. [Limit: > 0]
|
||||
|
||||
/// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
|
||||
float separationWeight;
|
||||
|
||||
/// Flags that impact steering behavior. (See: #UpdateFlags)
|
||||
unsigned char updateFlags;
|
||||
|
||||
/// The index of the avoidance configuration to use for the agent.
|
||||
/// [Limits: 0 <= value <= #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
|
||||
unsigned char obstacleAvoidanceType;
|
||||
|
||||
/// The index of the query filter used by this agent.
|
||||
unsigned char queryFilterType;
|
||||
|
||||
/// User defined data attached to the agent.
|
||||
void* userData;
|
||||
};
|
||||
|
||||
enum MoveRequestState
|
||||
{
|
||||
DT_CROWDAGENT_TARGET_NONE = 0,
|
||||
DT_CROWDAGENT_TARGET_FAILED,
|
||||
DT_CROWDAGENT_TARGET_VALID,
|
||||
DT_CROWDAGENT_TARGET_REQUESTING,
|
||||
DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE,
|
||||
DT_CROWDAGENT_TARGET_WAITING_FOR_PATH,
|
||||
DT_CROWDAGENT_TARGET_VELOCITY,
|
||||
};
|
||||
|
||||
/// Represents an agent managed by a #dtCrowd object.
|
||||
/// @ingroup crowd
|
||||
struct dtCrowdAgent
|
||||
{
|
||||
/// True if the agent is active, false if the agent is in an unused slot in the agent pool.
|
||||
bool active;
|
||||
|
||||
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
|
||||
unsigned char state;
|
||||
|
||||
/// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the requested position, else false.
|
||||
bool partial;
|
||||
|
||||
/// The path corridor the agent is using.
|
||||
dtPathCorridor corridor;
|
||||
|
||||
/// The local boundary data for the agent.
|
||||
dtLocalBoundary boundary;
|
||||
|
||||
/// Time since the agent's path corridor was optimized.
|
||||
float topologyOptTime;
|
||||
|
||||
/// The known neighbors of the agent.
|
||||
dtCrowdNeighbour neis[DT_CROWDAGENT_MAX_NEIGHBOURS];
|
||||
|
||||
/// The number of neighbors.
|
||||
int nneis;
|
||||
|
||||
/// The desired speed.
|
||||
float desiredSpeed;
|
||||
|
||||
float npos[3]; ///< The current agent position. [(x, y, z)]
|
||||
float disp[3]; ///< A temporary value used to accumulate agent displacement during iterative collision resolution. [(x, y, z)]
|
||||
float dvel[3]; ///< The desired velocity of the agent. Based on the current path, calculated from scratch each frame. [(x, y, z)]
|
||||
float nvel[3]; ///< The desired velocity adjusted by obstacle avoidance, calculated from scratch each frame. [(x, y, z)]
|
||||
float vel[3]; ///< The actual velocity of the agent. The change from nvel -> vel is constrained by max acceleration. [(x, y, z)]
|
||||
|
||||
/// The agent's configuration parameters.
|
||||
dtCrowdAgentParams params;
|
||||
|
||||
/// The local path corridor corners for the agent. (Staight path.) [(x, y, z) * #ncorners]
|
||||
float cornerVerts[DT_CROWDAGENT_MAX_CORNERS*3];
|
||||
|
||||
/// The local path corridor corner flags. (See: #dtStraightPathFlags) [(flags) * #ncorners]
|
||||
unsigned char cornerFlags[DT_CROWDAGENT_MAX_CORNERS];
|
||||
|
||||
/// The reference id of the polygon being entered at the corner. [(polyRef) * #ncorners]
|
||||
dtPolyRef cornerPolys[DT_CROWDAGENT_MAX_CORNERS];
|
||||
|
||||
/// The number of corners.
|
||||
int ncorners;
|
||||
|
||||
unsigned char targetState; ///< State of the movement request.
|
||||
dtPolyRef targetRef; ///< Target polyref of the movement request.
|
||||
float targetPos[3]; ///< Target position of the movement request (or velocity in case of DT_CROWDAGENT_TARGET_VELOCITY).
|
||||
dtPathQueueRef targetPathqRef; ///< Path finder ref.
|
||||
bool targetReplan; ///< Flag indicating that the current path is being replanned.
|
||||
float targetReplanTime; /// <Time since the agent's target was replanned.
|
||||
};
|
||||
|
||||
struct dtCrowdAgentAnimation
|
||||
{
|
||||
bool active;
|
||||
float initPos[3], startPos[3], endPos[3];
|
||||
dtPolyRef polyRef;
|
||||
float t, tmax;
|
||||
};
|
||||
|
||||
/// Crowd agent update flags.
|
||||
/// @ingroup crowd
|
||||
/// @see dtCrowdAgentParams::updateFlags
|
||||
enum UpdateFlags
|
||||
{
|
||||
DT_CROWD_ANTICIPATE_TURNS = 1,
|
||||
DT_CROWD_OBSTACLE_AVOIDANCE = 2,
|
||||
DT_CROWD_SEPARATION = 4,
|
||||
DT_CROWD_OPTIMIZE_VIS = 8, ///< Use #dtPathCorridor::optimizePathVisibility() to optimize the agent path.
|
||||
DT_CROWD_OPTIMIZE_TOPO = 16, ///< Use dtPathCorridor::optimizePathTopology() to optimize the agent path.
|
||||
};
|
||||
|
||||
struct dtCrowdAgentDebugInfo
|
||||
{
|
||||
int idx;
|
||||
float optStart[3], optEnd[3];
|
||||
dtObstacleAvoidanceDebugData* vod;
|
||||
};
|
||||
|
||||
/// Provides local steering behaviors for a group of agents.
|
||||
/// @ingroup crowd
|
||||
class dtCrowd
|
||||
{
|
||||
int m_maxAgents;
|
||||
dtCrowdAgent* m_agents;
|
||||
dtCrowdAgent** m_activeAgents;
|
||||
dtCrowdAgentAnimation* m_agentAnims;
|
||||
|
||||
dtPathQueue m_pathq;
|
||||
|
||||
dtObstacleAvoidanceParams m_obstacleQueryParams[DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS];
|
||||
dtObstacleAvoidanceQuery* m_obstacleQuery;
|
||||
|
||||
dtProximityGrid* m_grid;
|
||||
|
||||
dtPolyRef* m_pathResult;
|
||||
int m_maxPathResult;
|
||||
|
||||
float m_agentPlacementHalfExtents[3];
|
||||
|
||||
dtQueryFilter m_filters[DT_CROWD_MAX_QUERY_FILTER_TYPE];
|
||||
|
||||
float m_maxAgentRadius;
|
||||
|
||||
int m_velocitySampleCount;
|
||||
|
||||
dtNavMeshQuery* m_navquery;
|
||||
|
||||
void updateTopologyOptimization(dtCrowdAgent** agents, const int nagents, const float dt);
|
||||
void updateMoveRequest(const float dt);
|
||||
void checkPathValidity(dtCrowdAgent** agents, const int nagents, const float dt);
|
||||
|
||||
inline int getAgentIndex(const dtCrowdAgent* agent) const { return (int)(agent - m_agents); }
|
||||
|
||||
bool requestMoveTargetReplan(const int idx, dtPolyRef ref, const float* pos);
|
||||
|
||||
void purge();
|
||||
|
||||
public:
|
||||
dtCrowd();
|
||||
~dtCrowd();
|
||||
|
||||
/// Initializes the crowd.
|
||||
/// @param[in] maxAgents The maximum number of agents the crowd can manage. [Limit: >= 1]
|
||||
/// @param[in] maxAgentRadius The maximum radius of any agent that will be added to the crowd. [Limit: > 0]
|
||||
/// @param[in] nav The navigation mesh to use for planning.
|
||||
/// @return True if the initialization succeeded.
|
||||
bool init(const int maxAgents, const float maxAgentRadius, dtNavMesh* nav);
|
||||
|
||||
/// Sets the shared avoidance configuration for the specified index.
|
||||
/// @param[in] idx The index. [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
|
||||
/// @param[in] params The new configuration.
|
||||
void setObstacleAvoidanceParams(const int idx, const dtObstacleAvoidanceParams* params);
|
||||
|
||||
/// Gets the shared avoidance configuration for the specified index.
|
||||
/// @param[in] idx The index of the configuration to retreive.
|
||||
/// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
|
||||
/// @return The requested configuration.
|
||||
const dtObstacleAvoidanceParams* getObstacleAvoidanceParams(const int idx) const;
|
||||
|
||||
/// Gets the specified agent from the pool.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @return The requested agent.
|
||||
const dtCrowdAgent* getAgent(const int idx);
|
||||
|
||||
/// Gets the specified agent from the pool.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @return The requested agent.
|
||||
dtCrowdAgent* getEditableAgent(const int idx);
|
||||
|
||||
/// The maximum number of agents that can be managed by the object.
|
||||
/// @return The maximum number of agents.
|
||||
int getAgentCount() const;
|
||||
|
||||
/// Adds a new agent to the crowd.
|
||||
/// @param[in] pos The requested position of the agent. [(x, y, z)]
|
||||
/// @param[in] params The configutation of the agent.
|
||||
/// @return The index of the agent in the agent pool. Or -1 if the agent could not be added.
|
||||
int addAgent(const float* pos, const dtCrowdAgentParams* params);
|
||||
|
||||
/// Updates the specified agent's configuration.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @param[in] params The new agent configuration.
|
||||
void updateAgentParameters(const int idx, const dtCrowdAgentParams* params);
|
||||
|
||||
/// Removes the agent from the crowd.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
void removeAgent(const int idx);
|
||||
|
||||
/// Submits a new move request for the specified agent.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @param[in] ref The position's polygon reference.
|
||||
/// @param[in] pos The position within the polygon. [(x, y, z)]
|
||||
/// @return True if the request was successfully submitted.
|
||||
bool requestMoveTarget(const int idx, dtPolyRef ref, const float* pos);
|
||||
|
||||
/// Submits a new move request for the specified agent.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @param[in] vel The movement velocity. [(x, y, z)]
|
||||
/// @return True if the request was successfully submitted.
|
||||
bool requestMoveVelocity(const int idx, const float* vel);
|
||||
|
||||
/// Resets any request for the specified agent.
|
||||
/// @param[in] idx The agent index. [Limits: 0 <= value < #getAgentCount()]
|
||||
/// @return True if the request was successfully reseted.
|
||||
bool resetMoveTarget(const int idx);
|
||||
|
||||
/// Gets the active agents int the agent pool.
|
||||
/// @param[out] agents An array of agent pointers. [(#dtCrowdAgent *) * maxAgents]
|
||||
/// @param[in] maxAgents The size of the crowd agent array.
|
||||
/// @return The number of agents returned in @p agents.
|
||||
int getActiveAgents(dtCrowdAgent** agents, const int maxAgents);
|
||||
|
||||
/// Updates the steering and positions of all agents.
|
||||
/// @param[in] dt The time, in seconds, to update the simulation. [Limit: > 0]
|
||||
/// @param[out] debug A debug object to load with debug information. [Opt]
|
||||
void update(const float dt, dtCrowdAgentDebugInfo* debug);
|
||||
|
||||
/// Gets the filter used by the crowd.
|
||||
/// @return The filter used by the crowd.
|
||||
inline const dtQueryFilter* getFilter(const int i) const { return (i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE) ? &m_filters[i] : 0; }
|
||||
|
||||
/// Gets the filter used by the crowd.
|
||||
/// @return The filter used by the crowd.
|
||||
inline dtQueryFilter* getEditableFilter(const int i) { return (i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE) ? &m_filters[i] : 0; }
|
||||
|
||||
/// Gets the search halfExtents [(x, y, z)] used by the crowd for query operations.
|
||||
/// @return The search halfExtents used by the crowd. [(x, y, z)]
|
||||
const float* getQueryHalfExtents() const { return m_agentPlacementHalfExtents; }
|
||||
|
||||
/// Same as getQueryHalfExtents. Left to maintain backwards compatibility.
|
||||
/// @return The search halfExtents used by the crowd. [(x, y, z)]
|
||||
const float* getQueryExtents() const { return m_agentPlacementHalfExtents; }
|
||||
|
||||
/// Gets the velocity sample count.
|
||||
/// @return The velocity sample count.
|
||||
inline int getVelocitySampleCount() const { return m_velocitySampleCount; }
|
||||
|
||||
/// Gets the crowd's proximity grid.
|
||||
/// @return The crowd's proximity grid.
|
||||
const dtProximityGrid* getGrid() const { return m_grid; }
|
||||
|
||||
/// Gets the crowd's path request queue.
|
||||
/// @return The crowd's path request queue.
|
||||
const dtPathQueue* getPathQueue() const { return &m_pathq; }
|
||||
|
||||
/// Gets the query object used by the crowd.
|
||||
const dtNavMeshQuery* getNavMeshQuery() const { return m_navquery; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtCrowd(const dtCrowd&);
|
||||
dtCrowd& operator=(const dtCrowd&);
|
||||
};
|
||||
|
||||
/// Allocates a crowd object using the Detour allocator.
|
||||
/// @return A crowd object that is ready for initialization, or null on failure.
|
||||
/// @ingroup crowd
|
||||
dtCrowd* dtAllocCrowd();
|
||||
|
||||
/// Frees the specified crowd object using the Detour allocator.
|
||||
/// @param[in] ptr A crowd object allocated using #dtAllocCrowd
|
||||
/// @ingroup crowd
|
||||
void dtFreeCrowd(dtCrowd* ptr);
|
||||
|
||||
|
||||
#endif // DETOURCROWD_H
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This section contains detailed documentation for members that don't have
|
||||
// a source file. It reduces clutter in the main section of the header.
|
||||
|
||||
/**
|
||||
|
||||
@defgroup crowd Crowd
|
||||
|
||||
Members in this module implement local steering and dynamic avoidance features.
|
||||
|
||||
The crowd is the big beast of the navigation features. It not only handles a
|
||||
lot of the path management for you, but also local steering and dynamic
|
||||
avoidance between members of the crowd. I.e. It can keep your agents from
|
||||
running into each other.
|
||||
|
||||
Main class: #dtCrowd
|
||||
|
||||
The #dtNavMeshQuery and #dtPathCorridor classes provide perfectly good, easy
|
||||
to use path planning features. But in the end they only give you points that
|
||||
your navigation client should be moving toward. When it comes to deciding things
|
||||
like agent velocity and steering to avoid other agents, that is up to you to
|
||||
implement. Unless, of course, you decide to use #dtCrowd.
|
||||
|
||||
Basically, you add an agent to the crowd, providing various configuration
|
||||
settings such as maximum speed and acceleration. You also provide a local
|
||||
target to more toward. The crowd manager then provides, with every update, the
|
||||
new agent position and velocity for the frame. The movement will be
|
||||
constrained to the navigation mesh, and steering will be applied to ensure
|
||||
agents managed by the crowd do not collide with each other.
|
||||
|
||||
This is very powerful feature set. But it comes with limitations.
|
||||
|
||||
The biggest limitation is that you must give control of the agent's position
|
||||
completely over to the crowd manager. You can update things like maximum speed
|
||||
and acceleration. But in order for the crowd manager to do its thing, it can't
|
||||
allow you to constantly be giving it overrides to position and velocity. So
|
||||
you give up direct control of the agent's movement. It belongs to the crowd.
|
||||
|
||||
The second biggest limitation revolves around the fact that the crowd manager
|
||||
deals with local planning. So the agent's target should never be more than
|
||||
256 polygons aways from its current position. If it is, you risk
|
||||
your agent failing to reach its target. So you may still need to do long
|
||||
distance planning and provide the crowd manager with intermediate targets.
|
||||
|
||||
Other significant limitations:
|
||||
|
||||
- All agents using the crowd manager will use the same #dtQueryFilter.
|
||||
- Crowd management is relatively expensive. The maximum agents under crowd
|
||||
management at any one time is between 20 and 30. A good place to start
|
||||
is a maximum of 25 agents for 0.5ms per frame.
|
||||
|
||||
@note This is a summary list of members. Use the index or search
|
||||
feature to find minor members.
|
||||
|
||||
@struct dtCrowdAgentParams
|
||||
@see dtCrowdAgent, dtCrowd::addAgent(), dtCrowd::updateAgentParameters()
|
||||
|
||||
@var dtCrowdAgentParams::obstacleAvoidanceType
|
||||
@par
|
||||
|
||||
#dtCrowd permits agents to use different avoidance configurations. This value
|
||||
is the index of the #dtObstacleAvoidanceParams within the crowd.
|
||||
|
||||
@see dtObstacleAvoidanceParams, dtCrowd::setObstacleAvoidanceParams(),
|
||||
dtCrowd::getObstacleAvoidanceParams()
|
||||
|
||||
@var dtCrowdAgentParams::collisionQueryRange
|
||||
@par
|
||||
|
||||
Collision elements include other agents and navigation mesh boundaries.
|
||||
|
||||
This value is often based on the agent radius and/or maximum speed. E.g. radius * 8
|
||||
|
||||
@var dtCrowdAgentParams::pathOptimizationRange
|
||||
@par
|
||||
|
||||
Only applicalbe if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag.
|
||||
|
||||
This value is often based on the agent radius. E.g. radius * 30
|
||||
|
||||
@see dtPathCorridor::optimizePathVisibility()
|
||||
|
||||
@var dtCrowdAgentParams::separationWeight
|
||||
@par
|
||||
|
||||
A higher value will result in agents trying to stay farther away from each other at
|
||||
the cost of more difficult steering in tight spaces.
|
||||
|
||||
*/
|
||||
|
@ -0,0 +1,66 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#ifndef DETOURLOCALBOUNDARY_H
|
||||
#define DETOURLOCALBOUNDARY_H
|
||||
|
||||
#include "DetourNavMeshQuery.h"
|
||||
|
||||
|
||||
class dtLocalBoundary
|
||||
{
|
||||
static const int MAX_LOCAL_SEGS = 8;
|
||||
static const int MAX_LOCAL_POLYS = 16;
|
||||
|
||||
struct Segment
|
||||
{
|
||||
float s[6]; ///< Segment start/end
|
||||
float d; ///< Distance for pruning.
|
||||
};
|
||||
|
||||
float m_center[3];
|
||||
Segment m_segs[MAX_LOCAL_SEGS];
|
||||
int m_nsegs;
|
||||
|
||||
dtPolyRef m_polys[MAX_LOCAL_POLYS];
|
||||
int m_npolys;
|
||||
|
||||
void addSegment(const float dist, const float* s);
|
||||
|
||||
public:
|
||||
dtLocalBoundary();
|
||||
~dtLocalBoundary();
|
||||
|
||||
void reset();
|
||||
|
||||
void update(dtPolyRef ref, const float* pos, const float collisionQueryRange,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
bool isValid(dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
inline const float* getCenter() const { return m_center; }
|
||||
inline int getSegmentCount() const { return m_nsegs; }
|
||||
inline const float* getSegment(int i) const { return m_segs[i].s; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtLocalBoundary(const dtLocalBoundary&);
|
||||
dtLocalBoundary& operator=(const dtLocalBoundary&);
|
||||
};
|
||||
|
||||
#endif // DETOURLOCALBOUNDARY_H
|
@ -0,0 +1,159 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#ifndef DETOUROBSTACLEAVOIDANCE_H
|
||||
#define DETOUROBSTACLEAVOIDANCE_H
|
||||
|
||||
struct dtObstacleCircle
|
||||
{
|
||||
float p[3]; ///< Position of the obstacle
|
||||
float vel[3]; ///< Velocity of the obstacle
|
||||
float dvel[3]; ///< Velocity of the obstacle
|
||||
float rad; ///< Radius of the obstacle
|
||||
float dp[3], np[3]; ///< Use for side selection during sampling.
|
||||
};
|
||||
|
||||
struct dtObstacleSegment
|
||||
{
|
||||
float p[3], q[3]; ///< End points of the obstacle segment
|
||||
bool touch;
|
||||
};
|
||||
|
||||
|
||||
class dtObstacleAvoidanceDebugData
|
||||
{
|
||||
public:
|
||||
dtObstacleAvoidanceDebugData();
|
||||
~dtObstacleAvoidanceDebugData();
|
||||
|
||||
bool init(const int maxSamples);
|
||||
void reset();
|
||||
void addSample(const float* vel, const float ssize, const float pen,
|
||||
const float vpen, const float vcpen, const float spen, const float tpen);
|
||||
|
||||
void normalizeSamples();
|
||||
|
||||
inline int getSampleCount() const { return m_nsamples; }
|
||||
inline const float* getSampleVelocity(const int i) const { return &m_vel[i*3]; }
|
||||
inline float getSampleSize(const int i) const { return m_ssize[i]; }
|
||||
inline float getSamplePenalty(const int i) const { return m_pen[i]; }
|
||||
inline float getSampleDesiredVelocityPenalty(const int i) const { return m_vpen[i]; }
|
||||
inline float getSampleCurrentVelocityPenalty(const int i) const { return m_vcpen[i]; }
|
||||
inline float getSamplePreferredSidePenalty(const int i) const { return m_spen[i]; }
|
||||
inline float getSampleCollisionTimePenalty(const int i) const { return m_tpen[i]; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtObstacleAvoidanceDebugData(const dtObstacleAvoidanceDebugData&);
|
||||
dtObstacleAvoidanceDebugData& operator=(const dtObstacleAvoidanceDebugData&);
|
||||
|
||||
int m_nsamples;
|
||||
int m_maxSamples;
|
||||
float* m_vel;
|
||||
float* m_ssize;
|
||||
float* m_pen;
|
||||
float* m_vpen;
|
||||
float* m_vcpen;
|
||||
float* m_spen;
|
||||
float* m_tpen;
|
||||
};
|
||||
|
||||
dtObstacleAvoidanceDebugData* dtAllocObstacleAvoidanceDebugData();
|
||||
void dtFreeObstacleAvoidanceDebugData(dtObstacleAvoidanceDebugData* ptr);
|
||||
|
||||
|
||||
static const int DT_MAX_PATTERN_DIVS = 32; ///< Max numver of adaptive divs.
|
||||
static const int DT_MAX_PATTERN_RINGS = 4; ///< Max number of adaptive rings.
|
||||
|
||||
struct dtObstacleAvoidanceParams
|
||||
{
|
||||
float velBias;
|
||||
float weightDesVel;
|
||||
float weightCurVel;
|
||||
float weightSide;
|
||||
float weightToi;
|
||||
float horizTime;
|
||||
unsigned char gridSize; ///< grid
|
||||
unsigned char adaptiveDivs; ///< adaptive
|
||||
unsigned char adaptiveRings; ///< adaptive
|
||||
unsigned char adaptiveDepth; ///< adaptive
|
||||
};
|
||||
|
||||
class dtObstacleAvoidanceQuery
|
||||
{
|
||||
public:
|
||||
dtObstacleAvoidanceQuery();
|
||||
~dtObstacleAvoidanceQuery();
|
||||
|
||||
bool init(const int maxCircles, const int maxSegments);
|
||||
|
||||
void reset();
|
||||
|
||||
void addCircle(const float* pos, const float rad,
|
||||
const float* vel, const float* dvel);
|
||||
|
||||
void addSegment(const float* p, const float* q);
|
||||
|
||||
int sampleVelocityGrid(const float* pos, const float rad, const float vmax,
|
||||
const float* vel, const float* dvel, float* nvel,
|
||||
const dtObstacleAvoidanceParams* params,
|
||||
dtObstacleAvoidanceDebugData* debug = 0);
|
||||
|
||||
int sampleVelocityAdaptive(const float* pos, const float rad, const float vmax,
|
||||
const float* vel, const float* dvel, float* nvel,
|
||||
const dtObstacleAvoidanceParams* params,
|
||||
dtObstacleAvoidanceDebugData* debug = 0);
|
||||
|
||||
inline int getObstacleCircleCount() const { return m_ncircles; }
|
||||
const dtObstacleCircle* getObstacleCircle(const int i) { return &m_circles[i]; }
|
||||
|
||||
inline int getObstacleSegmentCount() const { return m_nsegments; }
|
||||
const dtObstacleSegment* getObstacleSegment(const int i) { return &m_segments[i]; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtObstacleAvoidanceQuery(const dtObstacleAvoidanceQuery&);
|
||||
dtObstacleAvoidanceQuery& operator=(const dtObstacleAvoidanceQuery&);
|
||||
|
||||
void prepare(const float* pos, const float* dvel);
|
||||
|
||||
float processSample(const float* vcand, const float cs,
|
||||
const float* pos, const float rad,
|
||||
const float* vel, const float* dvel,
|
||||
const float minPenalty,
|
||||
dtObstacleAvoidanceDebugData* debug);
|
||||
|
||||
dtObstacleAvoidanceParams m_params;
|
||||
float m_invHorizTime;
|
||||
float m_vmax;
|
||||
float m_invVmax;
|
||||
|
||||
int m_maxCircles;
|
||||
dtObstacleCircle* m_circles;
|
||||
int m_ncircles;
|
||||
|
||||
int m_maxSegments;
|
||||
dtObstacleSegment* m_segments;
|
||||
int m_nsegments;
|
||||
};
|
||||
|
||||
dtObstacleAvoidanceQuery* dtAllocObstacleAvoidanceQuery();
|
||||
void dtFreeObstacleAvoidanceQuery(dtObstacleAvoidanceQuery* ptr);
|
||||
|
||||
|
||||
#endif // DETOUROBSTACLEAVOIDANCE_H
|
@ -0,0 +1,151 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#ifndef DETOUTPATHCORRIDOR_H
|
||||
#define DETOUTPATHCORRIDOR_H
|
||||
|
||||
#include "DetourNavMeshQuery.h"
|
||||
|
||||
/// Represents a dynamic polygon corridor used to plan agent movement.
|
||||
/// @ingroup crowd, detour
|
||||
class dtPathCorridor
|
||||
{
|
||||
float m_pos[3];
|
||||
float m_target[3];
|
||||
|
||||
dtPolyRef* m_path;
|
||||
int m_npath;
|
||||
int m_maxPath;
|
||||
|
||||
public:
|
||||
dtPathCorridor();
|
||||
~dtPathCorridor();
|
||||
|
||||
/// Allocates the corridor's path buffer.
|
||||
/// @param[in] maxPath The maximum path size the corridor can handle.
|
||||
/// @return True if the initialization succeeded.
|
||||
bool init(const int maxPath);
|
||||
|
||||
/// Resets the path corridor to the specified position.
|
||||
/// @param[in] ref The polygon reference containing the position.
|
||||
/// @param[in] pos The new position in the corridor. [(x, y, z)]
|
||||
void reset(dtPolyRef ref, const float* pos);
|
||||
|
||||
/// Finds the corners in the corridor from the position toward the target. (The straightened path.)
|
||||
/// @param[out] cornerVerts The corner vertices. [(x, y, z) * cornerCount] [Size: <= maxCorners]
|
||||
/// @param[out] cornerFlags The flag for each corner. [(flag) * cornerCount] [Size: <= maxCorners]
|
||||
/// @param[out] cornerPolys The polygon reference for each corner. [(polyRef) * cornerCount]
|
||||
/// [Size: <= @p maxCorners]
|
||||
/// @param[in] maxCorners The maximum number of corners the buffers can hold.
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
/// @return The number of corners returned in the corner buffers. [0 <= value <= @p maxCorners]
|
||||
int findCorners(float* cornerVerts, unsigned char* cornerFlags,
|
||||
dtPolyRef* cornerPolys, const int maxCorners,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Attempts to optimize the path if the specified point is visible from the current position.
|
||||
/// @param[in] next The point to search toward. [(x, y, z])
|
||||
/// @param[in] pathOptimizationRange The maximum range to search. [Limit: > 0]
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
void optimizePathVisibility(const float* next, const float pathOptimizationRange,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Attempts to optimize the path using a local area search. (Partial replanning.)
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
bool optimizePathTopology(dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
bool moveOverOffmeshConnection(dtPolyRef offMeshConRef, dtPolyRef* refs,
|
||||
float* startPos, float* endPos,
|
||||
dtNavMeshQuery* navquery);
|
||||
|
||||
bool fixPathStart(dtPolyRef safeRef, const float* safePos);
|
||||
|
||||
bool trimInvalidPath(dtPolyRef safeRef, const float* safePos,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Checks the current corridor path to see if its polygon references remain valid.
|
||||
/// @param[in] maxLookAhead The number of polygons from the beginning of the corridor to search.
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
bool isValid(const int maxLookAhead, dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Moves the position from the current location to the desired location, adjusting the corridor
|
||||
/// as needed to reflect the change.
|
||||
/// @param[in] npos The desired new position. [(x, y, z)]
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
/// @return Returns true if move succeeded.
|
||||
bool movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Moves the target from the curent location to the desired location, adjusting the corridor
|
||||
/// as needed to reflect the change.
|
||||
/// @param[in] npos The desired new target position. [(x, y, z)]
|
||||
/// @param[in] navquery The query object used to build the corridor.
|
||||
/// @param[in] filter The filter to apply to the operation.
|
||||
/// @return Returns true if move succeeded.
|
||||
bool moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter);
|
||||
|
||||
/// Loads a new path and target into the corridor.
|
||||
/// @param[in] target The target location within the last polygon of the path. [(x, y, z)]
|
||||
/// @param[in] path The path corridor. [(polyRef) * @p npolys]
|
||||
/// @param[in] npath The number of polygons in the path.
|
||||
void setCorridor(const float* target, const dtPolyRef* polys, const int npath);
|
||||
|
||||
/// Gets the current position within the corridor. (In the first polygon.)
|
||||
/// @return The current position within the corridor.
|
||||
inline const float* getPos() const { return m_pos; }
|
||||
|
||||
/// Gets the current target within the corridor. (In the last polygon.)
|
||||
/// @return The current target within the corridor.
|
||||
inline const float* getTarget() const { return m_target; }
|
||||
|
||||
/// The polygon reference id of the first polygon in the corridor, the polygon containing the position.
|
||||
/// @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.)
|
||||
inline dtPolyRef getFirstPoly() const { return m_npath ? m_path[0] : 0; }
|
||||
|
||||
/// The polygon reference id of the last polygon in the corridor, the polygon containing the target.
|
||||
/// @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
|
||||
inline dtPolyRef getLastPoly() const { return m_npath ? m_path[m_npath-1] : 0; }
|
||||
|
||||
/// The corridor's path.
|
||||
/// @return The corridor's path. [(polyRef) * #getPathCount()]
|
||||
inline const dtPolyRef* getPath() const { return m_path; }
|
||||
|
||||
/// The number of polygons in the current corridor path.
|
||||
/// @return The number of polygons in the current corridor path.
|
||||
inline int getPathCount() const { return m_npath; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtPathCorridor(const dtPathCorridor&);
|
||||
dtPathCorridor& operator=(const dtPathCorridor&);
|
||||
};
|
||||
|
||||
int dtMergeCorridorStartMoved(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited);
|
||||
|
||||
int dtMergeCorridorEndMoved(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited);
|
||||
|
||||
int dtMergeCorridorStartShortcut(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited);
|
||||
|
||||
#endif // DETOUTPATHCORRIDOR_H
|
@ -0,0 +1,79 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#ifndef DETOURPATHQUEUE_H
|
||||
#define DETOURPATHQUEUE_H
|
||||
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
|
||||
static const unsigned int DT_PATHQ_INVALID = 0;
|
||||
|
||||
typedef unsigned int dtPathQueueRef;
|
||||
|
||||
class dtPathQueue
|
||||
{
|
||||
struct PathQuery
|
||||
{
|
||||
dtPathQueueRef ref;
|
||||
/// Path find start and end location.
|
||||
float startPos[3], endPos[3];
|
||||
dtPolyRef startRef, endRef;
|
||||
/// Result.
|
||||
dtPolyRef* path;
|
||||
int npath;
|
||||
/// State.
|
||||
dtStatus status;
|
||||
int keepAlive;
|
||||
const dtQueryFilter* filter; ///< TODO: This is potentially dangerous!
|
||||
};
|
||||
|
||||
static const int MAX_QUEUE = 8;
|
||||
PathQuery m_queue[MAX_QUEUE];
|
||||
dtPathQueueRef m_nextHandle;
|
||||
int m_maxPathSize;
|
||||
int m_queueHead;
|
||||
dtNavMeshQuery* m_navquery;
|
||||
|
||||
void purge();
|
||||
|
||||
public:
|
||||
dtPathQueue();
|
||||
~dtPathQueue();
|
||||
|
||||
bool init(const int maxPathSize, const int maxSearchNodeCount, dtNavMesh* nav);
|
||||
|
||||
void update(const int maxIters);
|
||||
|
||||
dtPathQueueRef request(dtPolyRef startRef, dtPolyRef endRef,
|
||||
const float* startPos, const float* endPos,
|
||||
const dtQueryFilter* filter);
|
||||
|
||||
dtStatus getRequestStatus(dtPathQueueRef ref) const;
|
||||
|
||||
dtStatus getPathResult(dtPathQueueRef ref, dtPolyRef* path, int* pathSize, const int maxPath);
|
||||
|
||||
inline const dtNavMeshQuery* getNavQuery() const { return m_navquery; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtPathQueue(const dtPathQueue&);
|
||||
dtPathQueue& operator=(const dtPathQueue&);
|
||||
};
|
||||
|
||||
#endif // DETOURPATHQUEUE_H
|
@ -0,0 +1,74 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#ifndef DETOURPROXIMITYGRID_H
|
||||
#define DETOURPROXIMITYGRID_H
|
||||
|
||||
class dtProximityGrid
|
||||
{
|
||||
float m_cellSize;
|
||||
float m_invCellSize;
|
||||
|
||||
struct Item
|
||||
{
|
||||
unsigned short id;
|
||||
short x,y;
|
||||
unsigned short next;
|
||||
};
|
||||
Item* m_pool;
|
||||
int m_poolHead;
|
||||
int m_poolSize;
|
||||
|
||||
unsigned short* m_buckets;
|
||||
int m_bucketsSize;
|
||||
|
||||
int m_bounds[4];
|
||||
|
||||
public:
|
||||
dtProximityGrid();
|
||||
~dtProximityGrid();
|
||||
|
||||
bool init(const int poolSize, const float cellSize);
|
||||
|
||||
void clear();
|
||||
|
||||
void addItem(const unsigned short id,
|
||||
const float minx, const float miny,
|
||||
const float maxx, const float maxy);
|
||||
|
||||
int queryItems(const float minx, const float miny,
|
||||
const float maxx, const float maxy,
|
||||
unsigned short* ids, const int maxIds) const;
|
||||
|
||||
int getItemCountAt(const int x, const int y) const;
|
||||
|
||||
inline const int* getBounds() const { return m_bounds; }
|
||||
inline float getCellSize() const { return m_cellSize; }
|
||||
|
||||
private:
|
||||
// Explicitly disabled copy constructor and copy assignment operator.
|
||||
dtProximityGrid(const dtProximityGrid&);
|
||||
dtProximityGrid& operator=(const dtProximityGrid&);
|
||||
};
|
||||
|
||||
dtProximityGrid* dtAllocProximityGrid();
|
||||
void dtFreeProximityGrid(dtProximityGrid* ptr);
|
||||
|
||||
|
||||
#endif // DETOURPROXIMITYGRID_H
|
||||
|
1450
lib/haxerecast/recastnavigation/DetourCrowd/Source/DetourCrowd.cpp
Normal file
1450
lib/haxerecast/recastnavigation/DetourCrowd/Source/DetourCrowd.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,137 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#include <float.h>
|
||||
#include <string.h>
|
||||
#include "DetourLocalBoundary.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourAssert.h"
|
||||
|
||||
|
||||
dtLocalBoundary::dtLocalBoundary() :
|
||||
m_nsegs(0),
|
||||
m_npolys(0)
|
||||
{
|
||||
dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
|
||||
}
|
||||
|
||||
dtLocalBoundary::~dtLocalBoundary()
|
||||
{
|
||||
}
|
||||
|
||||
void dtLocalBoundary::reset()
|
||||
{
|
||||
dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
|
||||
m_npolys = 0;
|
||||
m_nsegs = 0;
|
||||
}
|
||||
|
||||
void dtLocalBoundary::addSegment(const float dist, const float* s)
|
||||
{
|
||||
// Insert neighbour based on the distance.
|
||||
Segment* seg = 0;
|
||||
if (!m_nsegs)
|
||||
{
|
||||
// First, trivial accept.
|
||||
seg = &m_segs[0];
|
||||
}
|
||||
else if (dist >= m_segs[m_nsegs-1].d)
|
||||
{
|
||||
// Further than the last segment, skip.
|
||||
if (m_nsegs >= MAX_LOCAL_SEGS)
|
||||
return;
|
||||
// Last, trivial accept.
|
||||
seg = &m_segs[m_nsegs];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert inbetween.
|
||||
int i;
|
||||
for (i = 0; i < m_nsegs; ++i)
|
||||
if (dist <= m_segs[i].d)
|
||||
break;
|
||||
const int tgt = i+1;
|
||||
const int n = dtMin(m_nsegs-i, MAX_LOCAL_SEGS-tgt);
|
||||
dtAssert(tgt+n <= MAX_LOCAL_SEGS);
|
||||
if (n > 0)
|
||||
memmove(&m_segs[tgt], &m_segs[i], sizeof(Segment)*n);
|
||||
seg = &m_segs[i];
|
||||
}
|
||||
|
||||
seg->d = dist;
|
||||
memcpy(seg->s, s, sizeof(float)*6);
|
||||
|
||||
if (m_nsegs < MAX_LOCAL_SEGS)
|
||||
m_nsegs++;
|
||||
}
|
||||
|
||||
void dtLocalBoundary::update(dtPolyRef ref, const float* pos, const float collisionQueryRange,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
static const int MAX_SEGS_PER_POLY = DT_VERTS_PER_POLYGON*3;
|
||||
|
||||
if (!ref)
|
||||
{
|
||||
dtVset(m_center, FLT_MAX,FLT_MAX,FLT_MAX);
|
||||
m_nsegs = 0;
|
||||
m_npolys = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
dtVcopy(m_center, pos);
|
||||
|
||||
// First query non-overlapping polygons.
|
||||
navquery->findLocalNeighbourhood(ref, pos, collisionQueryRange,
|
||||
filter, m_polys, 0, &m_npolys, MAX_LOCAL_POLYS);
|
||||
|
||||
// Secondly, store all polygon edges.
|
||||
m_nsegs = 0;
|
||||
float segs[MAX_SEGS_PER_POLY*6];
|
||||
int nsegs = 0;
|
||||
for (int j = 0; j < m_npolys; ++j)
|
||||
{
|
||||
navquery->getPolyWallSegments(m_polys[j], filter, segs, 0, &nsegs, MAX_SEGS_PER_POLY);
|
||||
for (int k = 0; k < nsegs; ++k)
|
||||
{
|
||||
const float* s = &segs[k*6];
|
||||
// Skip too distant segments.
|
||||
float tseg;
|
||||
const float distSqr = dtDistancePtSegSqr2D(pos, s, s+3, tseg);
|
||||
if (distSqr > dtSqr(collisionQueryRange))
|
||||
continue;
|
||||
addSegment(distSqr, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool dtLocalBoundary::isValid(dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
if (!m_npolys)
|
||||
return false;
|
||||
|
||||
// Check that all polygons still pass query filter.
|
||||
for (int i = 0; i < m_npolys; ++i)
|
||||
{
|
||||
if (!navquery->isValidPolyRef(m_polys[i], filter))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -0,0 +1,619 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#include "DetourObstacleAvoidance.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourMath.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourAssert.h"
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
#include <new>
|
||||
|
||||
static const float DT_PI = 3.14159265f;
|
||||
|
||||
static int sweepCircleCircle(const float* c0, const float r0, const float* v,
|
||||
const float* c1, const float r1,
|
||||
float& tmin, float& tmax)
|
||||
{
|
||||
static const float EPS = 0.0001f;
|
||||
float s[3];
|
||||
dtVsub(s,c1,c0);
|
||||
float r = r0+r1;
|
||||
float c = dtVdot2D(s,s) - r*r;
|
||||
float a = dtVdot2D(v,v);
|
||||
if (a < EPS) return 0; // not moving
|
||||
|
||||
// Overlap, calc time to exit.
|
||||
float b = dtVdot2D(v,s);
|
||||
float d = b*b - a*c;
|
||||
if (d < 0.0f) return 0; // no intersection.
|
||||
a = 1.0f / a;
|
||||
const float rd = dtMathSqrtf(d);
|
||||
tmin = (b - rd) * a;
|
||||
tmax = (b + rd) * a;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int isectRaySeg(const float* ap, const float* u,
|
||||
const float* bp, const float* bq,
|
||||
float& t)
|
||||
{
|
||||
float v[3], w[3];
|
||||
dtVsub(v,bq,bp);
|
||||
dtVsub(w,ap,bp);
|
||||
float d = dtVperp2D(u,v);
|
||||
if (dtMathFabsf(d) < 1e-6f) return 0;
|
||||
d = 1.0f/d;
|
||||
t = dtVperp2D(v,w) * d;
|
||||
if (t < 0 || t > 1) return 0;
|
||||
float s = dtVperp2D(u,w) * d;
|
||||
if (s < 0 || s > 1) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
dtObstacleAvoidanceDebugData* dtAllocObstacleAvoidanceDebugData()
|
||||
{
|
||||
void* mem = dtAlloc(sizeof(dtObstacleAvoidanceDebugData), DT_ALLOC_PERM);
|
||||
if (!mem) return 0;
|
||||
return new(mem) dtObstacleAvoidanceDebugData;
|
||||
}
|
||||
|
||||
void dtFreeObstacleAvoidanceDebugData(dtObstacleAvoidanceDebugData* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
ptr->~dtObstacleAvoidanceDebugData();
|
||||
dtFree(ptr);
|
||||
}
|
||||
|
||||
|
||||
dtObstacleAvoidanceDebugData::dtObstacleAvoidanceDebugData() :
|
||||
m_nsamples(0),
|
||||
m_maxSamples(0),
|
||||
m_vel(0),
|
||||
m_ssize(0),
|
||||
m_pen(0),
|
||||
m_vpen(0),
|
||||
m_vcpen(0),
|
||||
m_spen(0),
|
||||
m_tpen(0)
|
||||
{
|
||||
}
|
||||
|
||||
dtObstacleAvoidanceDebugData::~dtObstacleAvoidanceDebugData()
|
||||
{
|
||||
dtFree(m_vel);
|
||||
dtFree(m_ssize);
|
||||
dtFree(m_pen);
|
||||
dtFree(m_vpen);
|
||||
dtFree(m_vcpen);
|
||||
dtFree(m_spen);
|
||||
dtFree(m_tpen);
|
||||
}
|
||||
|
||||
bool dtObstacleAvoidanceDebugData::init(const int maxSamples)
|
||||
{
|
||||
dtAssert(maxSamples);
|
||||
m_maxSamples = maxSamples;
|
||||
|
||||
m_vel = (float*)dtAlloc(sizeof(float)*3*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_vel)
|
||||
return false;
|
||||
m_pen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_pen)
|
||||
return false;
|
||||
m_ssize = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_ssize)
|
||||
return false;
|
||||
m_vpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_vpen)
|
||||
return false;
|
||||
m_vcpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_vcpen)
|
||||
return false;
|
||||
m_spen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_spen)
|
||||
return false;
|
||||
m_tpen = (float*)dtAlloc(sizeof(float)*m_maxSamples, DT_ALLOC_PERM);
|
||||
if (!m_tpen)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceDebugData::reset()
|
||||
{
|
||||
m_nsamples = 0;
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceDebugData::addSample(const float* vel, const float ssize, const float pen,
|
||||
const float vpen, const float vcpen, const float spen, const float tpen)
|
||||
{
|
||||
if (m_nsamples >= m_maxSamples)
|
||||
return;
|
||||
dtAssert(m_vel);
|
||||
dtAssert(m_ssize);
|
||||
dtAssert(m_pen);
|
||||
dtAssert(m_vpen);
|
||||
dtAssert(m_vcpen);
|
||||
dtAssert(m_spen);
|
||||
dtAssert(m_tpen);
|
||||
dtVcopy(&m_vel[m_nsamples*3], vel);
|
||||
m_ssize[m_nsamples] = ssize;
|
||||
m_pen[m_nsamples] = pen;
|
||||
m_vpen[m_nsamples] = vpen;
|
||||
m_vcpen[m_nsamples] = vcpen;
|
||||
m_spen[m_nsamples] = spen;
|
||||
m_tpen[m_nsamples] = tpen;
|
||||
m_nsamples++;
|
||||
}
|
||||
|
||||
static void normalizeArray(float* arr, const int n)
|
||||
{
|
||||
// Normalize penaly range.
|
||||
float minPen = FLT_MAX;
|
||||
float maxPen = -FLT_MAX;
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
minPen = dtMin(minPen, arr[i]);
|
||||
maxPen = dtMax(maxPen, arr[i]);
|
||||
}
|
||||
const float penRange = maxPen-minPen;
|
||||
const float s = penRange > 0.001f ? (1.0f / penRange) : 1;
|
||||
for (int i = 0; i < n; ++i)
|
||||
arr[i] = dtClamp((arr[i]-minPen)*s, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceDebugData::normalizeSamples()
|
||||
{
|
||||
normalizeArray(m_pen, m_nsamples);
|
||||
normalizeArray(m_vpen, m_nsamples);
|
||||
normalizeArray(m_vcpen, m_nsamples);
|
||||
normalizeArray(m_spen, m_nsamples);
|
||||
normalizeArray(m_tpen, m_nsamples);
|
||||
}
|
||||
|
||||
|
||||
dtObstacleAvoidanceQuery* dtAllocObstacleAvoidanceQuery()
|
||||
{
|
||||
void* mem = dtAlloc(sizeof(dtObstacleAvoidanceQuery), DT_ALLOC_PERM);
|
||||
if (!mem) return 0;
|
||||
return new(mem) dtObstacleAvoidanceQuery;
|
||||
}
|
||||
|
||||
void dtFreeObstacleAvoidanceQuery(dtObstacleAvoidanceQuery* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
ptr->~dtObstacleAvoidanceQuery();
|
||||
dtFree(ptr);
|
||||
}
|
||||
|
||||
|
||||
dtObstacleAvoidanceQuery::dtObstacleAvoidanceQuery() :
|
||||
m_invHorizTime(0),
|
||||
m_vmax(0),
|
||||
m_invVmax(0),
|
||||
m_maxCircles(0),
|
||||
m_circles(0),
|
||||
m_ncircles(0),
|
||||
m_maxSegments(0),
|
||||
m_segments(0),
|
||||
m_nsegments(0)
|
||||
{
|
||||
}
|
||||
|
||||
dtObstacleAvoidanceQuery::~dtObstacleAvoidanceQuery()
|
||||
{
|
||||
dtFree(m_circles);
|
||||
dtFree(m_segments);
|
||||
}
|
||||
|
||||
bool dtObstacleAvoidanceQuery::init(const int maxCircles, const int maxSegments)
|
||||
{
|
||||
m_maxCircles = maxCircles;
|
||||
m_ncircles = 0;
|
||||
m_circles = (dtObstacleCircle*)dtAlloc(sizeof(dtObstacleCircle)*m_maxCircles, DT_ALLOC_PERM);
|
||||
if (!m_circles)
|
||||
return false;
|
||||
memset(m_circles, 0, sizeof(dtObstacleCircle)*m_maxCircles);
|
||||
|
||||
m_maxSegments = maxSegments;
|
||||
m_nsegments = 0;
|
||||
m_segments = (dtObstacleSegment*)dtAlloc(sizeof(dtObstacleSegment)*m_maxSegments, DT_ALLOC_PERM);
|
||||
if (!m_segments)
|
||||
return false;
|
||||
memset(m_segments, 0, sizeof(dtObstacleSegment)*m_maxSegments);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceQuery::reset()
|
||||
{
|
||||
m_ncircles = 0;
|
||||
m_nsegments = 0;
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceQuery::addCircle(const float* pos, const float rad,
|
||||
const float* vel, const float* dvel)
|
||||
{
|
||||
if (m_ncircles >= m_maxCircles)
|
||||
return;
|
||||
|
||||
dtObstacleCircle* cir = &m_circles[m_ncircles++];
|
||||
dtVcopy(cir->p, pos);
|
||||
cir->rad = rad;
|
||||
dtVcopy(cir->vel, vel);
|
||||
dtVcopy(cir->dvel, dvel);
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceQuery::addSegment(const float* p, const float* q)
|
||||
{
|
||||
if (m_nsegments >= m_maxSegments)
|
||||
return;
|
||||
|
||||
dtObstacleSegment* seg = &m_segments[m_nsegments++];
|
||||
dtVcopy(seg->p, p);
|
||||
dtVcopy(seg->q, q);
|
||||
}
|
||||
|
||||
void dtObstacleAvoidanceQuery::prepare(const float* pos, const float* dvel)
|
||||
{
|
||||
// Prepare obstacles
|
||||
for (int i = 0; i < m_ncircles; ++i)
|
||||
{
|
||||
dtObstacleCircle* cir = &m_circles[i];
|
||||
|
||||
// Side
|
||||
const float* pa = pos;
|
||||
const float* pb = cir->p;
|
||||
|
||||
const float orig[3] = {0,0,0};
|
||||
float dv[3];
|
||||
dtVsub(cir->dp,pb,pa);
|
||||
dtVnormalize(cir->dp);
|
||||
dtVsub(dv, cir->dvel, dvel);
|
||||
|
||||
const float a = dtTriArea2D(orig, cir->dp,dv);
|
||||
if (a < 0.01f)
|
||||
{
|
||||
cir->np[0] = -cir->dp[2];
|
||||
cir->np[2] = cir->dp[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
cir->np[0] = cir->dp[2];
|
||||
cir->np[2] = -cir->dp[0];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_nsegments; ++i)
|
||||
{
|
||||
dtObstacleSegment* seg = &m_segments[i];
|
||||
|
||||
// Precalc if the agent is really close to the segment.
|
||||
const float r = 0.01f;
|
||||
float t;
|
||||
seg->touch = dtDistancePtSegSqr2D(pos, seg->p, seg->q, t) < dtSqr(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Calculate the collision penalty for a given velocity vector
|
||||
*
|
||||
* @param vcand sampled velocity
|
||||
* @param dvel desired velocity
|
||||
* @param minPenalty threshold penalty for early out
|
||||
*/
|
||||
float dtObstacleAvoidanceQuery::processSample(const float* vcand, const float cs,
|
||||
const float* pos, const float rad,
|
||||
const float* vel, const float* dvel,
|
||||
const float minPenalty,
|
||||
dtObstacleAvoidanceDebugData* debug)
|
||||
{
|
||||
// penalty for straying away from the desired and current velocities
|
||||
const float vpen = m_params.weightDesVel * (dtVdist2D(vcand, dvel) * m_invVmax);
|
||||
const float vcpen = m_params.weightCurVel * (dtVdist2D(vcand, vel) * m_invVmax);
|
||||
|
||||
// find the threshold hit time to bail out based on the early out penalty
|
||||
// (see how the penalty is calculated below to understnad)
|
||||
float minPen = minPenalty - vpen - vcpen;
|
||||
float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime;
|
||||
if (tThresold - m_params.horizTime > -FLT_EPSILON)
|
||||
return minPenalty; // already too much
|
||||
|
||||
// Find min time of impact and exit amongst all obstacles.
|
||||
float tmin = m_params.horizTime;
|
||||
float side = 0;
|
||||
int nside = 0;
|
||||
|
||||
for (int i = 0; i < m_ncircles; ++i)
|
||||
{
|
||||
const dtObstacleCircle* cir = &m_circles[i];
|
||||
|
||||
// RVO
|
||||
float vab[3];
|
||||
dtVscale(vab, vcand, 2);
|
||||
dtVsub(vab, vab, vel);
|
||||
dtVsub(vab, vab, cir->vel);
|
||||
|
||||
// Side
|
||||
side += dtClamp(dtMin(dtVdot2D(cir->dp,vab)*0.5f+0.5f, dtVdot2D(cir->np,vab)*2), 0.0f, 1.0f);
|
||||
nside++;
|
||||
|
||||
float htmin = 0, htmax = 0;
|
||||
if (!sweepCircleCircle(pos,rad, vab, cir->p,cir->rad, htmin, htmax))
|
||||
continue;
|
||||
|
||||
// Handle overlapping obstacles.
|
||||
if (htmin < 0.0f && htmax > 0.0f)
|
||||
{
|
||||
// Avoid more when overlapped.
|
||||
htmin = -htmin * 0.5f;
|
||||
}
|
||||
|
||||
if (htmin >= 0.0f)
|
||||
{
|
||||
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
|
||||
if (htmin < tmin)
|
||||
{
|
||||
tmin = htmin;
|
||||
if (tmin < tThresold)
|
||||
return minPenalty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_nsegments; ++i)
|
||||
{
|
||||
const dtObstacleSegment* seg = &m_segments[i];
|
||||
float htmin = 0;
|
||||
|
||||
if (seg->touch)
|
||||
{
|
||||
// Special case when the agent is very close to the segment.
|
||||
float sdir[3], snorm[3];
|
||||
dtVsub(sdir, seg->q, seg->p);
|
||||
snorm[0] = -sdir[2];
|
||||
snorm[2] = sdir[0];
|
||||
// If the velocity is pointing towards the segment, no collision.
|
||||
if (dtVdot2D(snorm, vcand) < 0.0f)
|
||||
continue;
|
||||
// Else immediate collision.
|
||||
htmin = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isectRaySeg(pos, vcand, seg->p, seg->q, htmin))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid less when facing walls.
|
||||
htmin *= 2.0f;
|
||||
|
||||
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
|
||||
if (htmin < tmin)
|
||||
{
|
||||
tmin = htmin;
|
||||
if (tmin < tThresold)
|
||||
return minPenalty;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize side bias, to prevent it dominating too much.
|
||||
if (nside)
|
||||
side /= nside;
|
||||
|
||||
const float spen = m_params.weightSide * side;
|
||||
const float tpen = m_params.weightToi * (1.0f/(0.1f+tmin*m_invHorizTime));
|
||||
|
||||
const float penalty = vpen + vcpen + spen + tpen;
|
||||
|
||||
// Store different penalties for debug viewing
|
||||
if (debug)
|
||||
debug->addSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
|
||||
|
||||
return penalty;
|
||||
}
|
||||
|
||||
int dtObstacleAvoidanceQuery::sampleVelocityGrid(const float* pos, const float rad, const float vmax,
|
||||
const float* vel, const float* dvel, float* nvel,
|
||||
const dtObstacleAvoidanceParams* params,
|
||||
dtObstacleAvoidanceDebugData* debug)
|
||||
{
|
||||
prepare(pos, dvel);
|
||||
|
||||
memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams));
|
||||
m_invHorizTime = 1.0f / m_params.horizTime;
|
||||
m_vmax = vmax;
|
||||
m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX;
|
||||
|
||||
dtVset(nvel, 0,0,0);
|
||||
|
||||
if (debug)
|
||||
debug->reset();
|
||||
|
||||
const float cvx = dvel[0] * m_params.velBias;
|
||||
const float cvz = dvel[2] * m_params.velBias;
|
||||
const float cs = vmax * 2 * (1 - m_params.velBias) / (float)(m_params.gridSize-1);
|
||||
const float half = (m_params.gridSize-1)*cs*0.5f;
|
||||
|
||||
float minPenalty = FLT_MAX;
|
||||
int ns = 0;
|
||||
|
||||
for (int y = 0; y < m_params.gridSize; ++y)
|
||||
{
|
||||
for (int x = 0; x < m_params.gridSize; ++x)
|
||||
{
|
||||
float vcand[3];
|
||||
vcand[0] = cvx + x*cs - half;
|
||||
vcand[1] = 0;
|
||||
vcand[2] = cvz + y*cs - half;
|
||||
|
||||
if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+cs/2)) continue;
|
||||
|
||||
const float penalty = processSample(vcand, cs, pos,rad,vel,dvel, minPenalty, debug);
|
||||
ns++;
|
||||
if (penalty < minPenalty)
|
||||
{
|
||||
minPenalty = penalty;
|
||||
dtVcopy(nvel, vcand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ns;
|
||||
}
|
||||
|
||||
|
||||
// vector normalization that ignores the y-component.
|
||||
inline void dtNormalize2D(float* v)
|
||||
{
|
||||
float d = dtMathSqrtf(v[0] * v[0] + v[2] * v[2]);
|
||||
if (d==0)
|
||||
return;
|
||||
d = 1.0f / d;
|
||||
v[0] *= d;
|
||||
v[2] *= d;
|
||||
}
|
||||
|
||||
// vector normalization that ignores the y-component.
|
||||
inline void dtRorate2D(float* dest, const float* v, float ang)
|
||||
{
|
||||
float c = cosf(ang);
|
||||
float s = sinf(ang);
|
||||
dest[0] = v[0]*c - v[2]*s;
|
||||
dest[2] = v[0]*s + v[2]*c;
|
||||
dest[1] = v[1];
|
||||
}
|
||||
|
||||
|
||||
int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const float rad, const float vmax,
|
||||
const float* vel, const float* dvel, float* nvel,
|
||||
const dtObstacleAvoidanceParams* params,
|
||||
dtObstacleAvoidanceDebugData* debug)
|
||||
{
|
||||
prepare(pos, dvel);
|
||||
|
||||
memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams));
|
||||
m_invHorizTime = 1.0f / m_params.horizTime;
|
||||
m_vmax = vmax;
|
||||
m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX;
|
||||
|
||||
dtVset(nvel, 0,0,0);
|
||||
|
||||
if (debug)
|
||||
debug->reset();
|
||||
|
||||
// Build sampling pattern aligned to desired velocity.
|
||||
float pat[(DT_MAX_PATTERN_DIVS*DT_MAX_PATTERN_RINGS+1)*2];
|
||||
int npat = 0;
|
||||
|
||||
const int ndivs = (int)m_params.adaptiveDivs;
|
||||
const int nrings= (int)m_params.adaptiveRings;
|
||||
const int depth = (int)m_params.adaptiveDepth;
|
||||
|
||||
const int nd = dtClamp(ndivs, 1, DT_MAX_PATTERN_DIVS);
|
||||
const int nr = dtClamp(nrings, 1, DT_MAX_PATTERN_RINGS);
|
||||
const float da = (1.0f/nd) * DT_PI*2;
|
||||
const float ca = cosf(da);
|
||||
const float sa = sinf(da);
|
||||
|
||||
// desired direction
|
||||
float ddir[6];
|
||||
dtVcopy(ddir, dvel);
|
||||
dtNormalize2D(ddir);
|
||||
dtRorate2D (ddir+3, ddir, da*0.5f); // rotated by da/2
|
||||
|
||||
// Always add sample at zero
|
||||
pat[npat*2+0] = 0;
|
||||
pat[npat*2+1] = 0;
|
||||
npat++;
|
||||
|
||||
for (int j = 0; j < nr; ++j)
|
||||
{
|
||||
const float r = (float)(nr-j)/(float)nr;
|
||||
pat[npat*2+0] = ddir[(j%2)*3] * r;
|
||||
pat[npat*2+1] = ddir[(j%2)*3+2] * r;
|
||||
float* last1 = pat + npat*2;
|
||||
float* last2 = last1;
|
||||
npat++;
|
||||
|
||||
for (int i = 1; i < nd-1; i+=2)
|
||||
{
|
||||
// get next point on the "right" (rotate CW)
|
||||
pat[npat*2+0] = last1[0]*ca + last1[1]*sa;
|
||||
pat[npat*2+1] = -last1[0]*sa + last1[1]*ca;
|
||||
// get next point on the "left" (rotate CCW)
|
||||
pat[npat*2+2] = last2[0]*ca - last2[1]*sa;
|
||||
pat[npat*2+3] = last2[0]*sa + last2[1]*ca;
|
||||
|
||||
last1 = pat + npat*2;
|
||||
last2 = last1 + 2;
|
||||
npat += 2;
|
||||
}
|
||||
|
||||
if ((nd&1) == 0)
|
||||
{
|
||||
pat[npat*2+2] = last2[0]*ca - last2[1]*sa;
|
||||
pat[npat*2+3] = last2[0]*sa + last2[1]*ca;
|
||||
npat++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Start sampling.
|
||||
float cr = vmax * (1.0f - m_params.velBias);
|
||||
float res[3];
|
||||
dtVset(res, dvel[0] * m_params.velBias, 0, dvel[2] * m_params.velBias);
|
||||
int ns = 0;
|
||||
|
||||
for (int k = 0; k < depth; ++k)
|
||||
{
|
||||
float minPenalty = FLT_MAX;
|
||||
float bvel[3];
|
||||
dtVset(bvel, 0,0,0);
|
||||
|
||||
for (int i = 0; i < npat; ++i)
|
||||
{
|
||||
float vcand[3];
|
||||
vcand[0] = res[0] + pat[i*2+0]*cr;
|
||||
vcand[1] = 0;
|
||||
vcand[2] = res[2] + pat[i*2+1]*cr;
|
||||
|
||||
if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+0.001f)) continue;
|
||||
|
||||
const float penalty = processSample(vcand,cr/10, pos,rad,vel,dvel, minPenalty, debug);
|
||||
ns++;
|
||||
if (penalty < minPenalty)
|
||||
{
|
||||
minPenalty = penalty;
|
||||
dtVcopy(bvel, vcand);
|
||||
}
|
||||
}
|
||||
|
||||
dtVcopy(res, bvel);
|
||||
|
||||
cr *= 0.5f;
|
||||
}
|
||||
|
||||
dtVcopy(nvel, res);
|
||||
|
||||
return ns;
|
||||
}
|
@ -0,0 +1,597 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#include <string.h>
|
||||
#include "DetourPathCorridor.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourAssert.h"
|
||||
#include "DetourAlloc.h"
|
||||
|
||||
|
||||
int dtMergeCorridorStartMoved(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = npath-1; i >= 0; --i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = nvisited-1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
// If no intersection found just return current path.
|
||||
if (furthestPath == -1 || furthestVisited == -1)
|
||||
return npath;
|
||||
|
||||
// Concatenate paths.
|
||||
|
||||
// Adjust beginning of the buffer to include the visited.
|
||||
const int req = nvisited - furthestVisited;
|
||||
const int orig = dtMin(furthestPath+1, npath);
|
||||
int size = dtMax(0, npath-orig);
|
||||
if (req+size > maxPath)
|
||||
size = maxPath-req;
|
||||
if (size)
|
||||
memmove(path+req, path+orig, size*sizeof(dtPolyRef));
|
||||
|
||||
// Store visited
|
||||
for (int i = 0; i < req; ++i)
|
||||
path[i] = visited[(nvisited-1)-i];
|
||||
|
||||
return req+size;
|
||||
}
|
||||
|
||||
int dtMergeCorridorEndMoved(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = 0; i < npath; ++i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = nvisited-1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
// If no intersection found just return current path.
|
||||
if (furthestPath == -1 || furthestVisited == -1)
|
||||
return npath;
|
||||
|
||||
// Concatenate paths.
|
||||
const int ppos = furthestPath+1;
|
||||
const int vpos = furthestVisited+1;
|
||||
const int count = dtMin(nvisited-vpos, maxPath-ppos);
|
||||
dtAssert(ppos+count <= maxPath);
|
||||
if (count)
|
||||
memcpy(path+ppos, visited+vpos, sizeof(dtPolyRef)*count);
|
||||
|
||||
return ppos+count;
|
||||
}
|
||||
|
||||
int dtMergeCorridorStartShortcut(dtPolyRef* path, const int npath, const int maxPath,
|
||||
const dtPolyRef* visited, const int nvisited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = npath-1; i >= 0; --i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = nvisited-1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
// If no intersection found just return current path.
|
||||
if (furthestPath == -1 || furthestVisited == -1)
|
||||
return npath;
|
||||
|
||||
// Concatenate paths.
|
||||
|
||||
// Adjust beginning of the buffer to include the visited.
|
||||
const int req = furthestVisited;
|
||||
if (req <= 0)
|
||||
return npath;
|
||||
|
||||
const int orig = furthestPath;
|
||||
int size = dtMax(0, npath-orig);
|
||||
if (req+size > maxPath)
|
||||
size = maxPath-req;
|
||||
if (size)
|
||||
memmove(path+req, path+orig, size*sizeof(dtPolyRef));
|
||||
|
||||
// Store visited
|
||||
for (int i = 0; i < req; ++i)
|
||||
path[i] = visited[i];
|
||||
|
||||
return req+size;
|
||||
}
|
||||
|
||||
/**
|
||||
@class dtPathCorridor
|
||||
@par
|
||||
|
||||
The corridor is loaded with a path, usually obtained from a #dtNavMeshQuery::findPath() query. The corridor
|
||||
is then used to plan local movement, with the corridor automatically updating as needed to deal with inaccurate
|
||||
agent locomotion.
|
||||
|
||||
Example of a common use case:
|
||||
|
||||
-# Construct the corridor object and call #init() to allocate its path buffer.
|
||||
-# Obtain a path from a #dtNavMeshQuery object.
|
||||
-# Use #reset() to set the agent's current position. (At the beginning of the path.)
|
||||
-# Use #setCorridor() to load the path and target.
|
||||
-# Use #findCorners() to plan movement. (This handles dynamic path straightening.)
|
||||
-# Use #movePosition() to feed agent movement back into the corridor. (The corridor will automatically adjust as needed.)
|
||||
-# If the target is moving, use #moveTargetPosition() to update the end of the corridor.
|
||||
(The corridor will automatically adjust as needed.)
|
||||
-# Repeat the previous 3 steps to continue to move the agent.
|
||||
|
||||
The corridor position and target are always constrained to the navigation mesh.
|
||||
|
||||
One of the difficulties in maintaining a path is that floating point errors, locomotion inaccuracies, and/or local
|
||||
steering can result in the agent crossing the boundary of the path corridor, temporarily invalidating the path.
|
||||
This class uses local mesh queries to detect and update the corridor as needed to handle these types of issues.
|
||||
|
||||
The fact that local mesh queries are used to move the position and target locations results in two beahviors that
|
||||
need to be considered:
|
||||
|
||||
Every time a move function is used there is a chance that the path will become non-optimial. Basically, the further
|
||||
the target is moved from its original location, and the further the position is moved outside the original corridor,
|
||||
the more likely the path will become non-optimal. This issue can be addressed by periodically running the
|
||||
#optimizePathTopology() and #optimizePathVisibility() methods.
|
||||
|
||||
All local mesh queries have distance limitations. (Review the #dtNavMeshQuery methods for details.) So the most accurate
|
||||
use case is to move the position and target in small increments. If a large increment is used, then the corridor
|
||||
may not be able to accurately find the new location. Because of this limiation, if a position is moved in a large
|
||||
increment, then compare the desired and resulting polygon references. If the two do not match, then path replanning
|
||||
may be needed. E.g. If you move the target, check #getLastPoly() to see if it is the expected polygon.
|
||||
|
||||
*/
|
||||
|
||||
dtPathCorridor::dtPathCorridor() :
|
||||
m_path(0),
|
||||
m_npath(0),
|
||||
m_maxPath(0)
|
||||
{
|
||||
}
|
||||
|
||||
dtPathCorridor::~dtPathCorridor()
|
||||
{
|
||||
dtFree(m_path);
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// @warning Cannot be called more than once.
|
||||
bool dtPathCorridor::init(const int maxPath)
|
||||
{
|
||||
dtAssert(!m_path);
|
||||
m_path = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*maxPath, DT_ALLOC_PERM);
|
||||
if (!m_path)
|
||||
return false;
|
||||
m_npath = 0;
|
||||
m_maxPath = maxPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// Essentially, the corridor is set of one polygon in size with the target
|
||||
/// equal to the position.
|
||||
void dtPathCorridor::reset(dtPolyRef ref, const float* pos)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtVcopy(m_pos, pos);
|
||||
dtVcopy(m_target, pos);
|
||||
m_path[0] = ref;
|
||||
m_npath = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
This is the function used to plan local movement within the corridor. One or more corners can be
|
||||
detected in order to plan movement. It performs essentially the same function as #dtNavMeshQuery::findStraightPath.
|
||||
|
||||
Due to internal optimizations, the maximum number of corners returned will be (@p maxCorners - 1)
|
||||
For example: If the buffers are sized to hold 10 corners, the function will never return more than 9 corners.
|
||||
So if 10 corners are needed, the buffers should be sized for 11 corners.
|
||||
|
||||
If the target is within range, it will be the last corner and have a polygon reference id of zero.
|
||||
*/
|
||||
int dtPathCorridor::findCorners(float* cornerVerts, unsigned char* cornerFlags,
|
||||
dtPolyRef* cornerPolys, const int maxCorners,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* /*filter*/)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtAssert(m_npath);
|
||||
|
||||
static const float MIN_TARGET_DIST = 0.01f;
|
||||
|
||||
int ncorners = 0;
|
||||
navquery->findStraightPath(m_pos, m_target, m_path, m_npath,
|
||||
cornerVerts, cornerFlags, cornerPolys, &ncorners, maxCorners);
|
||||
|
||||
// Prune points in the beginning of the path which are too close.
|
||||
while (ncorners)
|
||||
{
|
||||
if ((cornerFlags[0] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) ||
|
||||
dtVdist2DSqr(&cornerVerts[0], m_pos) > dtSqr(MIN_TARGET_DIST))
|
||||
break;
|
||||
ncorners--;
|
||||
if (ncorners)
|
||||
{
|
||||
memmove(cornerFlags, cornerFlags+1, sizeof(unsigned char)*ncorners);
|
||||
memmove(cornerPolys, cornerPolys+1, sizeof(dtPolyRef)*ncorners);
|
||||
memmove(cornerVerts, cornerVerts+3, sizeof(float)*3*ncorners);
|
||||
}
|
||||
}
|
||||
|
||||
// Prune points after an off-mesh connection.
|
||||
for (int i = 0; i < ncorners; ++i)
|
||||
{
|
||||
if (cornerFlags[i] & DT_STRAIGHTPATH_OFFMESH_CONNECTION)
|
||||
{
|
||||
ncorners = i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ncorners;
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
Inaccurate locomotion or dynamic obstacle avoidance can force the argent position significantly outside the
|
||||
original corridor. Over time this can result in the formation of a non-optimal corridor. Non-optimal paths can
|
||||
also form near the corners of tiles.
|
||||
|
||||
This function uses an efficient local visibility search to try to optimize the corridor
|
||||
between the current position and @p next.
|
||||
|
||||
The corridor will change only if @p next is visible from the current position and moving directly toward the point
|
||||
is better than following the existing path.
|
||||
|
||||
The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency
|
||||
of the call to match the needs to the agent.
|
||||
|
||||
This function is not suitable for long distance searches.
|
||||
*/
|
||||
void dtPathCorridor::optimizePathVisibility(const float* next, const float pathOptimizationRange,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
|
||||
// Clamp the ray to max distance.
|
||||
float goal[3];
|
||||
dtVcopy(goal, next);
|
||||
float dist = dtVdist2D(m_pos, goal);
|
||||
|
||||
// If too close to the goal, do not try to optimize.
|
||||
if (dist < 0.01f)
|
||||
return;
|
||||
|
||||
// Overshoot a little. This helps to optimize open fields in tiled meshes.
|
||||
dist = dtMin(dist+0.01f, pathOptimizationRange);
|
||||
|
||||
// Adjust ray length.
|
||||
float delta[3];
|
||||
dtVsub(delta, goal, m_pos);
|
||||
dtVmad(goal, m_pos, delta, pathOptimizationRange/dist);
|
||||
|
||||
static const int MAX_RES = 32;
|
||||
dtPolyRef res[MAX_RES];
|
||||
float t, norm[3];
|
||||
int nres = 0;
|
||||
navquery->raycast(m_path[0], m_pos, goal, filter, &t, norm, res, &nres, MAX_RES);
|
||||
if (nres > 1 && t > 0.99f)
|
||||
{
|
||||
m_npath = dtMergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, nres);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
|
||||
original corridor. Over time this can result in the formation of a non-optimal corridor. This function will use a
|
||||
local area path search to try to re-optimize the corridor.
|
||||
|
||||
The more inaccurate the agent movement, the more beneficial this function becomes. Simply adjust the frequency of
|
||||
the call to match the needs to the agent.
|
||||
*/
|
||||
bool dtPathCorridor::optimizePathTopology(dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(navquery);
|
||||
dtAssert(filter);
|
||||
dtAssert(m_path);
|
||||
|
||||
if (m_npath < 3)
|
||||
return false;
|
||||
|
||||
static const int MAX_ITER = 32;
|
||||
static const int MAX_RES = 32;
|
||||
|
||||
dtPolyRef res[MAX_RES];
|
||||
int nres = 0;
|
||||
navquery->initSlicedFindPath(m_path[0], m_path[m_npath-1], m_pos, m_target, filter);
|
||||
navquery->updateSlicedFindPath(MAX_ITER, 0);
|
||||
dtStatus status = navquery->finalizeSlicedFindPathPartial(m_path, m_npath, res, &nres, MAX_RES);
|
||||
|
||||
if (dtStatusSucceed(status) && nres > 0)
|
||||
{
|
||||
m_npath = dtMergeCorridorStartShortcut(m_path, m_npath, m_maxPath, res, nres);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dtPathCorridor::moveOverOffmeshConnection(dtPolyRef offMeshConRef, dtPolyRef* refs,
|
||||
float* startPos, float* endPos,
|
||||
dtNavMeshQuery* navquery)
|
||||
{
|
||||
dtAssert(navquery);
|
||||
dtAssert(m_path);
|
||||
dtAssert(m_npath);
|
||||
|
||||
// Advance the path up to and over the off-mesh connection.
|
||||
dtPolyRef prevRef = 0, polyRef = m_path[0];
|
||||
int npos = 0;
|
||||
while (npos < m_npath && polyRef != offMeshConRef)
|
||||
{
|
||||
prevRef = polyRef;
|
||||
polyRef = m_path[npos];
|
||||
npos++;
|
||||
}
|
||||
if (npos == m_npath)
|
||||
{
|
||||
// Could not find offMeshConRef
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prune path
|
||||
for (int i = npos; i < m_npath; ++i)
|
||||
m_path[i-npos] = m_path[i];
|
||||
m_npath -= npos;
|
||||
|
||||
refs[0] = prevRef;
|
||||
refs[1] = polyRef;
|
||||
|
||||
const dtNavMesh* nav = navquery->getAttachedNavMesh();
|
||||
dtAssert(nav);
|
||||
|
||||
dtStatus status = nav->getOffMeshConnectionPolyEndPoints(refs[0], refs[1], startPos, endPos);
|
||||
if (dtStatusSucceed(status))
|
||||
{
|
||||
dtVcopy(m_pos, endPos);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
Behavior:
|
||||
|
||||
- The movement is constrained to the surface of the navigation mesh.
|
||||
- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid.
|
||||
- The new position will be located in the adjusted corridor's first polygon.
|
||||
|
||||
The expected use case is that the desired position will be 'near' the current corridor. What is considered 'near'
|
||||
depends on local polygon density, query search half extents, etc.
|
||||
|
||||
The resulting position will differ from the desired position if the desired position is not on the navigation mesh,
|
||||
or it can't be reached using a local search.
|
||||
*/
|
||||
bool dtPathCorridor::movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtAssert(m_npath);
|
||||
|
||||
// Move along navmesh and update new position.
|
||||
float result[3];
|
||||
static const int MAX_VISITED = 16;
|
||||
dtPolyRef visited[MAX_VISITED];
|
||||
int nvisited = 0;
|
||||
dtStatus status = navquery->moveAlongSurface(m_path[0], m_pos, npos, filter,
|
||||
result, visited, &nvisited, MAX_VISITED);
|
||||
if (dtStatusSucceed(status)) {
|
||||
m_npath = dtMergeCorridorStartMoved(m_path, m_npath, m_maxPath, visited, nvisited);
|
||||
|
||||
// Adjust the position to stay on top of the navmesh.
|
||||
float h = m_pos[1];
|
||||
navquery->getPolyHeight(m_path[0], result, &h);
|
||||
result[1] = h;
|
||||
dtVcopy(m_pos, result);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@par
|
||||
|
||||
Behavior:
|
||||
|
||||
- The movement is constrained to the surface of the navigation mesh.
|
||||
- The corridor is automatically adjusted (shorted or lengthened) in order to remain valid.
|
||||
- The new target will be located in the adjusted corridor's last polygon.
|
||||
|
||||
The expected use case is that the desired target will be 'near' the current corridor. What is considered 'near' depends on local polygon density, query search half extents, etc.
|
||||
|
||||
The resulting target will differ from the desired target if the desired target is not on the navigation mesh, or it can't be reached using a local search.
|
||||
*/
|
||||
bool dtPathCorridor::moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtAssert(m_npath);
|
||||
|
||||
// Move along navmesh and update new position.
|
||||
float result[3];
|
||||
static const int MAX_VISITED = 16;
|
||||
dtPolyRef visited[MAX_VISITED];
|
||||
int nvisited = 0;
|
||||
dtStatus status = navquery->moveAlongSurface(m_path[m_npath-1], m_target, npos, filter,
|
||||
result, visited, &nvisited, MAX_VISITED);
|
||||
if (dtStatusSucceed(status))
|
||||
{
|
||||
m_npath = dtMergeCorridorEndMoved(m_path, m_npath, m_maxPath, visited, nvisited);
|
||||
// TODO: should we do that?
|
||||
// Adjust the position to stay on top of the navmesh.
|
||||
/* float h = m_target[1];
|
||||
navquery->getPolyHeight(m_path[m_npath-1], result, &h);
|
||||
result[1] = h;*/
|
||||
|
||||
dtVcopy(m_target, result);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// The current corridor position is expected to be within the first polygon in the path. The target
|
||||
/// is expected to be in the last polygon.
|
||||
///
|
||||
/// @warning The size of the path must not exceed the size of corridor's path buffer set during #init().
|
||||
void dtPathCorridor::setCorridor(const float* target, const dtPolyRef* path, const int npath)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
dtAssert(npath > 0);
|
||||
dtAssert(npath < m_maxPath);
|
||||
|
||||
dtVcopy(m_target, target);
|
||||
memcpy(m_path, path, sizeof(dtPolyRef)*npath);
|
||||
m_npath = npath;
|
||||
}
|
||||
|
||||
bool dtPathCorridor::fixPathStart(dtPolyRef safeRef, const float* safePos)
|
||||
{
|
||||
dtAssert(m_path);
|
||||
|
||||
dtVcopy(m_pos, safePos);
|
||||
if (m_npath < 3 && m_npath > 0)
|
||||
{
|
||||
m_path[2] = m_path[m_npath-1];
|
||||
m_path[0] = safeRef;
|
||||
m_path[1] = 0;
|
||||
m_npath = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_path[0] = safeRef;
|
||||
m_path[1] = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dtPathCorridor::trimInvalidPath(dtPolyRef safeRef, const float* safePos,
|
||||
dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
dtAssert(navquery);
|
||||
dtAssert(filter);
|
||||
dtAssert(m_path);
|
||||
|
||||
// Keep valid path as far as possible.
|
||||
int n = 0;
|
||||
while (n < m_npath && navquery->isValidPolyRef(m_path[n], filter)) {
|
||||
n++;
|
||||
}
|
||||
|
||||
if (n == m_npath)
|
||||
{
|
||||
// All valid, no need to fix.
|
||||
return true;
|
||||
}
|
||||
else if (n == 0)
|
||||
{
|
||||
// The first polyref is bad, use current safe values.
|
||||
dtVcopy(m_pos, safePos);
|
||||
m_path[0] = safeRef;
|
||||
m_npath = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The path is partially usable.
|
||||
m_npath = n;
|
||||
}
|
||||
|
||||
// Clamp target pos to last poly
|
||||
float tgt[3];
|
||||
dtVcopy(tgt, m_target);
|
||||
navquery->closestPointOnPolyBoundary(m_path[m_npath-1], tgt, m_target);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @par
|
||||
///
|
||||
/// The path can be invalidated if there are structural changes to the underlying navigation mesh, or the state of
|
||||
/// a polygon within the path changes resulting in it being filtered out. (E.g. An exclusion or inclusion flag changes.)
|
||||
bool dtPathCorridor::isValid(const int maxLookAhead, dtNavMeshQuery* navquery, const dtQueryFilter* filter)
|
||||
{
|
||||
// Check that all polygons still pass query filter.
|
||||
const int n = dtMin(m_npath, maxLookAhead);
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
if (!navquery->isValidPolyRef(m_path[i], filter))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#include <string.h>
|
||||
#include "DetourPathQueue.h"
|
||||
#include "DetourNavMesh.h"
|
||||
#include "DetourNavMeshQuery.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourCommon.h"
|
||||
|
||||
|
||||
dtPathQueue::dtPathQueue() :
|
||||
m_nextHandle(1),
|
||||
m_maxPathSize(0),
|
||||
m_queueHead(0),
|
||||
m_navquery(0)
|
||||
{
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
m_queue[i].path = 0;
|
||||
}
|
||||
|
||||
dtPathQueue::~dtPathQueue()
|
||||
{
|
||||
purge();
|
||||
}
|
||||
|
||||
void dtPathQueue::purge()
|
||||
{
|
||||
dtFreeNavMeshQuery(m_navquery);
|
||||
m_navquery = 0;
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
dtFree(m_queue[i].path);
|
||||
m_queue[i].path = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool dtPathQueue::init(const int maxPathSize, const int maxSearchNodeCount, dtNavMesh* nav)
|
||||
{
|
||||
purge();
|
||||
|
||||
m_navquery = dtAllocNavMeshQuery();
|
||||
if (!m_navquery)
|
||||
return false;
|
||||
if (dtStatusFailed(m_navquery->init(nav, maxSearchNodeCount)))
|
||||
return false;
|
||||
|
||||
m_maxPathSize = maxPathSize;
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
m_queue[i].ref = DT_PATHQ_INVALID;
|
||||
m_queue[i].path = (dtPolyRef*)dtAlloc(sizeof(dtPolyRef)*m_maxPathSize, DT_ALLOC_PERM);
|
||||
if (!m_queue[i].path)
|
||||
return false;
|
||||
}
|
||||
|
||||
m_queueHead = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dtPathQueue::update(const int maxIters)
|
||||
{
|
||||
static const int MAX_KEEP_ALIVE = 2; // in update ticks.
|
||||
|
||||
// Update path request until there is nothing to update
|
||||
// or upto maxIters pathfinder iterations has been consumed.
|
||||
int iterCount = maxIters;
|
||||
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
PathQuery& q = m_queue[m_queueHead % MAX_QUEUE];
|
||||
|
||||
// Skip inactive requests.
|
||||
if (q.ref == DT_PATHQ_INVALID)
|
||||
{
|
||||
m_queueHead++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle completed request.
|
||||
if (dtStatusSucceed(q.status) || dtStatusFailed(q.status))
|
||||
{
|
||||
// If the path result has not been read in few frames, free the slot.
|
||||
q.keepAlive++;
|
||||
if (q.keepAlive > MAX_KEEP_ALIVE)
|
||||
{
|
||||
q.ref = DT_PATHQ_INVALID;
|
||||
q.status = 0;
|
||||
}
|
||||
|
||||
m_queueHead++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle query start.
|
||||
if (q.status == 0)
|
||||
{
|
||||
q.status = m_navquery->initSlicedFindPath(q.startRef, q.endRef, q.startPos, q.endPos, q.filter);
|
||||
}
|
||||
// Handle query in progress.
|
||||
if (dtStatusInProgress(q.status))
|
||||
{
|
||||
int iters = 0;
|
||||
q.status = m_navquery->updateSlicedFindPath(iterCount, &iters);
|
||||
iterCount -= iters;
|
||||
}
|
||||
if (dtStatusSucceed(q.status))
|
||||
{
|
||||
q.status = m_navquery->finalizeSlicedFindPath(q.path, &q.npath, m_maxPathSize);
|
||||
}
|
||||
|
||||
if (iterCount <= 0)
|
||||
break;
|
||||
|
||||
m_queueHead++;
|
||||
}
|
||||
}
|
||||
|
||||
dtPathQueueRef dtPathQueue::request(dtPolyRef startRef, dtPolyRef endRef,
|
||||
const float* startPos, const float* endPos,
|
||||
const dtQueryFilter* filter)
|
||||
{
|
||||
// Find empty slot
|
||||
int slot = -1;
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
if (m_queue[i].ref == DT_PATHQ_INVALID)
|
||||
{
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Could not find slot.
|
||||
if (slot == -1)
|
||||
return DT_PATHQ_INVALID;
|
||||
|
||||
dtPathQueueRef ref = m_nextHandle++;
|
||||
if (m_nextHandle == DT_PATHQ_INVALID) m_nextHandle++;
|
||||
|
||||
PathQuery& q = m_queue[slot];
|
||||
q.ref = ref;
|
||||
dtVcopy(q.startPos, startPos);
|
||||
q.startRef = startRef;
|
||||
dtVcopy(q.endPos, endPos);
|
||||
q.endRef = endRef;
|
||||
|
||||
q.status = 0;
|
||||
q.npath = 0;
|
||||
q.filter = filter;
|
||||
q.keepAlive = 0;
|
||||
|
||||
return ref;
|
||||
}
|
||||
|
||||
dtStatus dtPathQueue::getRequestStatus(dtPathQueueRef ref) const
|
||||
{
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
if (m_queue[i].ref == ref)
|
||||
return m_queue[i].status;
|
||||
}
|
||||
return DT_FAILURE;
|
||||
}
|
||||
|
||||
dtStatus dtPathQueue::getPathResult(dtPathQueueRef ref, dtPolyRef* path, int* pathSize, const int maxPath)
|
||||
{
|
||||
for (int i = 0; i < MAX_QUEUE; ++i)
|
||||
{
|
||||
if (m_queue[i].ref == ref)
|
||||
{
|
||||
PathQuery& q = m_queue[i];
|
||||
dtStatus details = q.status & DT_STATUS_DETAIL_MASK;
|
||||
// Free request for reuse.
|
||||
q.ref = DT_PATHQ_INVALID;
|
||||
q.status = 0;
|
||||
// Copy path
|
||||
int n = dtMin(q.npath, maxPath);
|
||||
memcpy(path, q.path, sizeof(dtPolyRef)*n);
|
||||
*pathSize = n;
|
||||
return details | DT_SUCCESS;
|
||||
}
|
||||
}
|
||||
return DT_FAILURE;
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
//
|
||||
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
|
||||
#include <string.h>
|
||||
#include <new>
|
||||
#include "DetourProximityGrid.h"
|
||||
#include "DetourCommon.h"
|
||||
#include "DetourMath.h"
|
||||
#include "DetourAlloc.h"
|
||||
#include "DetourAssert.h"
|
||||
|
||||
|
||||
dtProximityGrid* dtAllocProximityGrid()
|
||||
{
|
||||
void* mem = dtAlloc(sizeof(dtProximityGrid), DT_ALLOC_PERM);
|
||||
if (!mem) return 0;
|
||||
return new(mem) dtProximityGrid;
|
||||
}
|
||||
|
||||
void dtFreeProximityGrid(dtProximityGrid* ptr)
|
||||
{
|
||||
if (!ptr) return;
|
||||
ptr->~dtProximityGrid();
|
||||
dtFree(ptr);
|
||||
}
|
||||
|
||||
|
||||
inline int hashPos2(int x, int y, int n)
|
||||
{
|
||||
return ((x*73856093) ^ (y*19349663)) & (n-1);
|
||||
}
|
||||
|
||||
|
||||
dtProximityGrid::dtProximityGrid() :
|
||||
m_cellSize(0),
|
||||
m_invCellSize(0),
|
||||
m_pool(0),
|
||||
m_poolHead(0),
|
||||
m_poolSize(0),
|
||||
m_buckets(0),
|
||||
m_bucketsSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
dtProximityGrid::~dtProximityGrid()
|
||||
{
|
||||
dtFree(m_buckets);
|
||||
dtFree(m_pool);
|
||||
}
|
||||
|
||||
bool dtProximityGrid::init(const int poolSize, const float cellSize)
|
||||
{
|
||||
dtAssert(poolSize > 0);
|
||||
dtAssert(cellSize > 0.0f);
|
||||
|
||||
m_cellSize = cellSize;
|
||||
m_invCellSize = 1.0f / m_cellSize;
|
||||
|
||||
// Allocate hashs buckets
|
||||
m_bucketsSize = dtNextPow2(poolSize);
|
||||
m_buckets = (unsigned short*)dtAlloc(sizeof(unsigned short)*m_bucketsSize, DT_ALLOC_PERM);
|
||||
if (!m_buckets)
|
||||
return false;
|
||||
|
||||
// Allocate pool of items.
|
||||
m_poolSize = poolSize;
|
||||
m_poolHead = 0;
|
||||
m_pool = (Item*)dtAlloc(sizeof(Item)*m_poolSize, DT_ALLOC_PERM);
|
||||
if (!m_pool)
|
||||
return false;
|
||||
|
||||
clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dtProximityGrid::clear()
|
||||
{
|
||||
memset(m_buckets, 0xff, sizeof(unsigned short)*m_bucketsSize);
|
||||
m_poolHead = 0;
|
||||
m_bounds[0] = 0xffff;
|
||||
m_bounds[1] = 0xffff;
|
||||
m_bounds[2] = -0xffff;
|
||||
m_bounds[3] = -0xffff;
|
||||
}
|
||||
|
||||
void dtProximityGrid::addItem(const unsigned short id,
|
||||
const float minx, const float miny,
|
||||
const float maxx, const float maxy)
|
||||
{
|
||||
const int iminx = (int)dtMathFloorf(minx * m_invCellSize);
|
||||
const int iminy = (int)dtMathFloorf(miny * m_invCellSize);
|
||||
const int imaxx = (int)dtMathFloorf(maxx * m_invCellSize);
|
||||
const int imaxy = (int)dtMathFloorf(maxy * m_invCellSize);
|
||||
|
||||
m_bounds[0] = dtMin(m_bounds[0], iminx);
|
||||
m_bounds[1] = dtMin(m_bounds[1], iminy);
|
||||
m_bounds[2] = dtMax(m_bounds[2], imaxx);
|
||||
m_bounds[3] = dtMax(m_bounds[3], imaxy);
|
||||
|
||||
for (int y = iminy; y <= imaxy; ++y)
|
||||
{
|
||||
for (int x = iminx; x <= imaxx; ++x)
|
||||
{
|
||||
if (m_poolHead < m_poolSize)
|
||||
{
|
||||
const int h = hashPos2(x, y, m_bucketsSize);
|
||||
const unsigned short idx = (unsigned short)m_poolHead;
|
||||
m_poolHead++;
|
||||
Item& item = m_pool[idx];
|
||||
item.x = (short)x;
|
||||
item.y = (short)y;
|
||||
item.id = id;
|
||||
item.next = m_buckets[h];
|
||||
m_buckets[h] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int dtProximityGrid::queryItems(const float minx, const float miny,
|
||||
const float maxx, const float maxy,
|
||||
unsigned short* ids, const int maxIds) const
|
||||
{
|
||||
const int iminx = (int)dtMathFloorf(minx * m_invCellSize);
|
||||
const int iminy = (int)dtMathFloorf(miny * m_invCellSize);
|
||||
const int imaxx = (int)dtMathFloorf(maxx * m_invCellSize);
|
||||
const int imaxy = (int)dtMathFloorf(maxy * m_invCellSize);
|
||||
|
||||
int n = 0;
|
||||
|
||||
for (int y = iminy; y <= imaxy; ++y)
|
||||
{
|
||||
for (int x = iminx; x <= imaxx; ++x)
|
||||
{
|
||||
const int h = hashPos2(x, y, m_bucketsSize);
|
||||
unsigned short idx = m_buckets[h];
|
||||
while (idx != 0xffff)
|
||||
{
|
||||
Item& item = m_pool[idx];
|
||||
if ((int)item.x == x && (int)item.y == y)
|
||||
{
|
||||
// Check if the id exists already.
|
||||
const unsigned short* end = ids + n;
|
||||
unsigned short* i = ids;
|
||||
while (i != end && *i != item.id)
|
||||
++i;
|
||||
// Item not found, add it.
|
||||
if (i == end)
|
||||
{
|
||||
if (n >= maxIds)
|
||||
return n;
|
||||
ids[n++] = item.id;
|
||||
}
|
||||
}
|
||||
idx = item.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int dtProximityGrid::getItemCountAt(const int x, const int y) const
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
const int h = hashPos2(x, y, m_bucketsSize);
|
||||
unsigned short idx = m_buckets[h];
|
||||
while (idx != 0xffff)
|
||||
{
|
||||
Item& item = m_pool[idx];
|
||||
if ((int)item.x == x && (int)item.y == y)
|
||||
n++;
|
||||
idx = item.next;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
Reference in New Issue
Block a user