diff --git a/Engine/lib/recast/CMakeLists.txt b/Engine/lib/recast/CMakeLists.txt deleted file mode 100644 index 8dd69f639a..0000000000 --- a/Engine/lib/recast/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) - -PROJECT(RecastNavigation) -#SET(RECAST_VERSION r129) - -IF(NOT CMAKE_BUILD_TYPE) -# SET(CMAKE_BUILD_TYPE "Debug") - SET(CMAKE_BUILD_TYPE "Release") -ENDIF(NOT CMAKE_BUILD_TYPE) - -IF(MSVC) - OPTION(USE_MSVC_FAST_FLOATINGPOINT "Use MSVC /fp:fast option" ON) - IF(USE_MSVC_FAST_FLOATINGPOINT) - ADD_DEFINITIONS(/fp:fast) - ENDIF(USE_MSVC_FAST_FLOATINGPOINT) -ENDIF(MSVC) - -IF(WIN32) - ADD_DEFINITIONS(/D _CRT_SECURE_NO_WARNINGS) -ENDIF(WIN32) - -ADD_SUBDIRECTORY(DebugUtils) -ADD_SUBDIRECTORY(Detour) -ADD_SUBDIRECTORY(DetourCrowd) -ADD_SUBDIRECTORY(DetourTileCache) -ADD_SUBDIRECTORY(Recast) -ADD_SUBDIRECTORY(RecastDemo) diff --git a/Engine/lib/recast/DebugUtils/CMakeLists.txt b/Engine/lib/recast/DebugUtils/CMakeLists.txt deleted file mode 100644 index e79364ab29..0000000000 --- a/Engine/lib/recast/DebugUtils/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) - -SET(debugutils_SRCS - Source/DebugDraw.cpp - Source/DetourDebugDraw.cpp - Source/RecastDebugDraw.cpp - Source/RecastDump.cpp -) - -SET(debugutils_HDRS - Include/DebugDraw.h - Include/DetourDebugDraw.h - Include/RecastDebugDraw.h - Include/RecastDump.h -) - -INCLUDE_DIRECTORIES(Include - ../Detour/Include - ../DetourTileCache/Include - ../Recast/Include -) - -ADD_LIBRARY(DebugUtils ${debugutils_SRCS} ${debugutils_HDRS}) diff --git a/Engine/lib/recast/DebugUtils/Include/DebugDraw.h b/Engine/lib/recast/DebugUtils/Include/DebugDraw.h index b24094fb20..0b8c59352c 100644 --- a/Engine/lib/recast/DebugUtils/Include/DebugDraw.h +++ b/Engine/lib/recast/DebugUtils/Include/DebugDraw.h @@ -210,6 +210,10 @@ class duDisplayList : public duDebugDraw virtual void end(); void clear(); void draw(struct duDebugDraw* dd); +private: + // Explicitly disabled copy constructor and copy assignment operator. + duDisplayList(const duDisplayList&); + duDisplayList& operator=(const duDisplayList&); }; diff --git a/Engine/lib/recast/DebugUtils/Include/DetourDebugDraw.h b/Engine/lib/recast/DebugUtils/Include/DetourDebugDraw.h index 34d93e1e59..ff2ca2f9d1 100644 --- a/Engine/lib/recast/DebugUtils/Include/DetourDebugDraw.h +++ b/Engine/lib/recast/DebugUtils/Include/DetourDebugDraw.h @@ -45,4 +45,4 @@ void duDebugDrawTileCacheContours(duDebugDraw* dd, const struct dtTileCacheConto void duDebugDrawTileCachePolyMesh(duDebugDraw* dd, const struct dtTileCachePolyMesh& lmesh, const float* orig, const float cs, const float ch); -#endif // DETOURDEBUGDRAW_H \ No newline at end of file +#endif // DETOURDEBUGDRAW_H diff --git a/Engine/lib/recast/DebugUtils/Include/RecastDebugDraw.h b/Engine/lib/recast/DebugUtils/Include/RecastDebugDraw.h index f75802d05a..6a55fa6472 100644 --- a/Engine/lib/recast/DebugUtils/Include/RecastDebugDraw.h +++ b/Engine/lib/recast/DebugUtils/Include/RecastDebugDraw.h @@ -33,10 +33,6 @@ void duDebugDrawHeightfieldLayer(duDebugDraw* dd, const struct rcHeightfieldLaye void duDebugDrawHeightfieldLayers(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset); void duDebugDrawHeightfieldLayersRegions(duDebugDraw* dd, const struct rcHeightfieldLayerSet& lset); -void duDebugDrawLayerContours(duDebugDraw* dd, const struct rcLayerContourSet& lcset); -void duDebugDrawLayerPolyMesh(duDebugDraw* dd, const struct rcLayerPolyMesh& lmesh); - - void duDebugDrawRegionConnections(struct duDebugDraw* dd, const struct rcContourSet& cset, const float alpha = 1.0f); void duDebugDrawRawContours(struct duDebugDraw* dd, const struct rcContourSet& cset, const float alpha = 1.0f); void duDebugDrawContours(struct duDebugDraw* dd, const struct rcContourSet& cset, const float alpha = 1.0f); diff --git a/Engine/lib/recast/DebugUtils/Source/DebugDraw.cpp b/Engine/lib/recast/DebugUtils/Source/DebugDraw.cpp index 982bdba32f..a009def364 100644 --- a/Engine/lib/recast/DebugUtils/Source/DebugDraw.cpp +++ b/Engine/lib/recast/DebugUtils/Source/DebugDraw.cpp @@ -17,9 +17,9 @@ // #define _USE_MATH_DEFINES -#include #include #include "DebugDraw.h" +#include "DetourMath.h" duDebugDraw::~duDebugDraw() @@ -180,8 +180,8 @@ void duAppendCylinderWire(struct duDebugDraw* dd, float minx, float miny, float for (int i = 0; i < NUM_SEG; ++i) { const float a = (float)i/(float)NUM_SEG*DU_PI*2; - dir[i*2] = cosf(a); - dir[i*2+1] = sinf(a); + dir[i*2] = dtMathCosf(a); + dir[i*2+1] = dtMathSinf(a); } } diff --git a/Engine/lib/recast/DebugUtils/Source/DetourDebugDraw.cpp b/Engine/lib/recast/DebugUtils/Source/DetourDebugDraw.cpp index d9b7783275..e2db1078f9 100644 --- a/Engine/lib/recast/DebugUtils/Source/DetourDebugDraw.cpp +++ b/Engine/lib/recast/DebugUtils/Source/DetourDebugDraw.cpp @@ -16,7 +16,6 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include #include "DebugDraw.h" #include "DetourDebugDraw.h" #include "DetourNavMesh.h" diff --git a/Engine/lib/recast/DebugUtils/Source/RecastDump.cpp b/Engine/lib/recast/DebugUtils/Source/RecastDump.cpp index 7663fc7557..2093825157 100644 --- a/Engine/lib/recast/DebugUtils/Source/RecastDump.cpp +++ b/Engine/lib/recast/DebugUtils/Source/RecastDump.cpp @@ -350,7 +350,7 @@ bool duReadCompactHeightfield(struct rcCompactHeightfield& chf, duFileIO* io) io->read(&chf.walkableHeight, sizeof(chf.walkableHeight)); io->read(&chf.walkableClimb, sizeof(chf.walkableClimb)); - io->write(&chf.borderSize, sizeof(chf.borderSize)); + io->read(&chf.borderSize, sizeof(chf.borderSize)); io->read(&chf.maxDistance, sizeof(chf.maxDistance)); io->read(&chf.maxRegions, sizeof(chf.maxRegions)); diff --git a/Engine/lib/recast/Detour/CMakeLists.txt b/Engine/lib/recast/Detour/CMakeLists.txt deleted file mode 100644 index e05ed11fa6..0000000000 --- a/Engine/lib/recast/Detour/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) - -SET(detour_SRCS - Source/DetourAlloc.cpp - Source/DetourCommon.cpp - Source/DetourNavMesh.cpp - Source/DetourNavMeshBuilder.cpp - Source/DetourNavMeshQuery.cpp - Source/DetourNode.cpp -) - -SET(detour_HDRS - Include/DetourAlloc.h - Include/DetourAssert.h - Include/DetourCommon.h - Include/DetourNavMesh.h - Include/DetourNavMeshBuilder.h - Include/DetourNavMeshQuery.h - Include/DetourNode.h -) - -INCLUDE_DIRECTORIES(Include) - -ADD_LIBRARY(Detour ${detour_SRCS} ${detour_HDRS}) diff --git a/Engine/lib/recast/Detour/Include/DetourAlloc.h b/Engine/lib/recast/Detour/Include/DetourAlloc.h index e814b62a71..f87b454acb 100644 --- a/Engine/lib/recast/Detour/Include/DetourAlloc.h +++ b/Engine/lib/recast/Detour/Include/DetourAlloc.h @@ -19,6 +19,8 @@ #ifndef DETOURALLOCATOR_H #define DETOURALLOCATOR_H +#include + /// Provides hint values to the memory allocator on how long the /// memory is expected to be used. enum dtAllocHint @@ -32,7 +34,7 @@ enum dtAllocHint // @param[in] rcAllocHint A hint to the allocator on how long the memory is expected to be in use. // @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. /// @see dtAllocSetCustom -typedef void* (dtAllocFunc)(int size, dtAllocHint hint); +typedef void* (dtAllocFunc)(size_t size, dtAllocHint hint); /// A memory deallocation function. /// @param[in] ptr A pointer to a memory block previously allocated using #dtAllocFunc. @@ -49,7 +51,7 @@ void dtAllocSetCustom(dtAllocFunc *allocFunc, dtFreeFunc *freeFunc); /// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. /// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. /// @see dtFree -void* dtAlloc(int size, dtAllocHint hint); +void* dtAlloc(size_t size, dtAllocHint hint); /// Deallocates a memory block. /// @param[in] ptr A pointer to a memory block previously allocated using #dtAlloc. diff --git a/Engine/lib/recast/Detour/Include/DetourCommon.h b/Engine/lib/recast/Detour/Include/DetourCommon.h index 34f46d8d38..2afba0d780 100644 --- a/Engine/lib/recast/Detour/Include/DetourCommon.h +++ b/Engine/lib/recast/Detour/Include/DetourCommon.h @@ -19,6 +19,8 @@ #ifndef DETOURCOMMON_H #define DETOURCOMMON_H +#include "DetourMath.h" + /** @defgroup detour Detour @@ -32,6 +34,11 @@ feature to find minor members. /// @name General helper functions /// @{ +/// Used to ignore a function parameter. VS complains about unused parameters +/// and this silences the warning. +/// @param [in] _ Unused parameter +template void dtIgnoreUnused(const T&) { } + /// Swaps the values of the two parameters. /// @param[in,out] a Value A /// @param[in,out] b Value B @@ -66,11 +73,6 @@ template inline T dtSqr(T a) { return a*a; } /// @return The value, clamped to the specified range. template inline T dtClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); } -/// Returns the square root of the value. -/// @param[in] x The value. -/// @return The square root of the vlaue. -float dtSqrt(float x); - /// @} /// @name Vector helper functions. /// @{ @@ -197,7 +199,7 @@ inline void dtVcopy(float* dest, const float* a) /// @return The scalar length of the vector. inline float dtVlen(const float* v) { - return dtSqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); + return dtMathSqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); } /// Derives the square of the scalar length of the vector. (len * len) @@ -217,7 +219,7 @@ inline float dtVdist(const float* v1, const float* v2) const float dx = v2[0] - v1[0]; const float dy = v2[1] - v1[1]; const float dz = v2[2] - v1[2]; - return dtSqrt(dx*dx + dy*dy + dz*dz); + return dtMathSqrtf(dx*dx + dy*dy + dz*dz); } /// Returns the square of the distance between two points. @@ -242,7 +244,7 @@ inline float dtVdist2D(const float* v1, const float* v2) { const float dx = v2[0] - v1[0]; const float dz = v2[2] - v1[2]; - return dtSqrt(dx*dx + dz*dz); + return dtMathSqrtf(dx*dx + dz*dz); } /// Derives the square of the distance between the specified points on the xz-plane. @@ -260,7 +262,7 @@ inline float dtVdist2DSqr(const float* v1, const float* v2) /// @param[in,out] v The vector to normalize. [(x, y, z)] inline void dtVnormalize(float* v) { - float d = 1.0f / dtSqrt(dtSqr(v[0]) + dtSqr(v[1]) + dtSqr(v[2])); + float d = 1.0f / dtMathSqrtf(dtSqr(v[0]) + dtSqr(v[1]) + dtSqr(v[2])); v[0] *= d; v[1] *= d; v[2] *= d; @@ -376,6 +378,10 @@ bool dtIntersectSegmentPoly2D(const float* p0, const float* p1, float& tmin, float& tmax, int& segMin, int& segMax); +bool dtIntersectSegSeg2D(const float* ap, const float* aq, + const float* bp, const float* bq, + float& s, float& t); + /// Determines if the specified point is inside the convex polygon on the xz-plane. /// @param[in] pt The point to check. [(x, y, z)] /// @param[in] verts The polygon vertices. [(x, y, z) * @p nverts] diff --git a/Engine/lib/recast/Detour/Include/DetourMath.h b/Engine/lib/recast/Detour/Include/DetourMath.h new file mode 100644 index 0000000000..95e14f8843 --- /dev/null +++ b/Engine/lib/recast/Detour/Include/DetourMath.h @@ -0,0 +1,20 @@ +/** +@defgroup detour Detour + +Members in this module are wrappers around the standard math library +*/ + +#ifndef DETOURMATH_H +#define DETOURMATH_H + +#include + +inline float dtMathFabsf(float x) { return fabsf(x); } +inline float dtMathSqrtf(float x) { return sqrtf(x); } +inline float dtMathFloorf(float x) { return floorf(x); } +inline float dtMathCeilf(float x) { return ceilf(x); } +inline float dtMathCosf(float x) { return cosf(x); } +inline float dtMathSinf(float x) { return sinf(x); } +inline float dtMathAtan2f(float y, float x) { return atan2f(y, x); } + +#endif diff --git a/Engine/lib/recast/Detour/Include/DetourNavMesh.h b/Engine/lib/recast/Detour/Include/DetourNavMesh.h index d4fbe96746..8ecd57e460 100644 --- a/Engine/lib/recast/Detour/Include/DetourNavMesh.h +++ b/Engine/lib/recast/Detour/Include/DetourNavMesh.h @@ -22,16 +22,39 @@ #include "DetourAlloc.h" #include "DetourStatus.h" +// Undefine (or define in a build cofnig) the following line to use 64bit polyref. +// Generally not needed, useful for very large worlds. +// Note: tiles build using 32bit refs are not compatible with 64bit refs! +//#define DT_POLYREF64 1 + +#ifdef DT_POLYREF64 +// TODO: figure out a multiplatform version of uint64_t +// - maybe: https://code.google.com/p/msinttypes/ +// - or: http://www.azillionmonkeys.com/qed/pstdint.h +#include +#endif + // Note: If you want to use 64-bit refs, change the types of both dtPolyRef & dtTileRef. // It is also recommended that you change dtHashRef() to a proper 64-bit hash. /// A handle to a polygon within a navigation mesh tile. /// @ingroup detour +#ifdef DT_POLYREF64 +static const unsigned int DT_SALT_BITS = 16; +static const unsigned int DT_TILE_BITS = 28; +static const unsigned int DT_POLY_BITS = 20; +typedef uint64_t dtPolyRef; +#else typedef unsigned int dtPolyRef; +#endif /// A handle to a tile within a navigation mesh. /// @ingroup detour +#ifdef DT_POLYREF64 +typedef uint64_t dtTileRef; +#else typedef unsigned int dtTileRef; +#endif /// The maximum number of vertices per navigation polygon. /// @ingroup detour @@ -87,6 +110,31 @@ enum dtStraightPathFlags DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04, ///< The vertex is the start of an off-mesh connection. }; +/// Options for dtNavMeshQuery::findStraightPath. +enum dtStraightPathOptions +{ + DT_STRAIGHTPATH_AREA_CROSSINGS = 0x01, ///< Add a vertex at every polygon edge crossing where area changes. + DT_STRAIGHTPATH_ALL_CROSSINGS = 0x02, ///< Add a vertex at every polygon edge crossing. +}; + + +/// Options for dtNavMeshQuery::initSlicedFindPath and updateSlicedFindPath +enum dtFindPathOptions +{ + DT_FINDPATH_ANY_ANGLE = 0x02, ///< use raycasts during pathfind to "shortcut" (raycast still consider costs) +}; + +/// Options for dtNavMeshQuery::raycast +enum dtRaycastOptions +{ + DT_RAYCAST_USE_COSTS = 0x01, ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost +}; + + +/// Limit raycasting during any angle pahfinding +/// The limit is given as a multiple of the character radius +static const float DT_RAY_CAST_LIMIT_PROPORTIONS = 50.0f; + /// Flags representing the type of a navigation mesh polygon. enum dtPolyTypes { @@ -97,7 +145,7 @@ enum dtPolyTypes }; -/// Defines a polyogn within a dtMeshTile object. +/// Defines a polygon within a dtMeshTile object. /// @ingroup detour struct dtPoly { @@ -252,6 +300,9 @@ struct dtMeshTile int dataSize; ///< Size of the tile data. int flags; ///< Tile flags. (See: #dtTileFlags) dtMeshTile* next; ///< The next free tile, or the next tile in the spatial grid. +private: + dtMeshTile(const dtMeshTile&); + dtMeshTile& operator=(const dtMeshTile&); }; /// Configuration parameters used to define multi-tile navigation meshes. @@ -462,7 +513,11 @@ class dtNavMesh /// @param[in] ip The index of the polygon within the tile. inline dtPolyRef encodePolyId(unsigned int salt, unsigned int it, unsigned int ip) const { +#ifdef DT_POLYREF64 + return ((dtPolyRef)salt << (DT_POLY_BITS+DT_TILE_BITS)) | ((dtPolyRef)it << DT_POLY_BITS) | (dtPolyRef)ip; +#else return ((dtPolyRef)salt << (m_polyBits+m_tileBits)) | ((dtPolyRef)it << m_polyBits) | (dtPolyRef)ip; +#endif } /// Decodes a standard polygon reference. @@ -474,12 +529,21 @@ class dtNavMesh /// @see #encodePolyId inline void decodePolyId(dtPolyRef ref, unsigned int& salt, unsigned int& it, unsigned int& ip) const { +#ifdef DT_POLYREF64 + const dtPolyRef saltMask = ((dtPolyRef)1<> (DT_POLY_BITS+DT_TILE_BITS)) & saltMask); + it = (unsigned int)((ref >> DT_POLY_BITS) & tileMask); + ip = (unsigned int)(ref & polyMask); +#else const dtPolyRef saltMask = ((dtPolyRef)1<> (m_polyBits+m_tileBits)) & saltMask); it = (unsigned int)((ref >> m_polyBits) & tileMask); ip = (unsigned int)(ref & polyMask); +#endif } /// Extracts a tile's salt value from the specified polygon reference. @@ -488,8 +552,13 @@ class dtNavMesh /// @see #encodePolyId inline unsigned int decodePolyIdSalt(dtPolyRef ref) const { +#ifdef DT_POLYREF64 + const dtPolyRef saltMask = ((dtPolyRef)1<> (DT_POLY_BITS+DT_TILE_BITS)) & saltMask); +#else const dtPolyRef saltMask = ((dtPolyRef)1<> (m_polyBits+m_tileBits)) & saltMask); +#endif } /// Extracts the tile's index from the specified polygon reference. @@ -498,8 +567,13 @@ class dtNavMesh /// @see #encodePolyId inline unsigned int decodePolyIdTile(dtPolyRef ref) const { +#ifdef DT_POLYREF64 + const dtPolyRef tileMask = ((dtPolyRef)1<> DT_POLY_BITS) & tileMask); +#else const dtPolyRef tileMask = ((dtPolyRef)1<> m_polyBits) & tileMask); +#endif } /// Extracts the polygon's index (within its tile) from the specified polygon reference. @@ -508,13 +582,21 @@ class dtNavMesh /// @see #encodePolyId inline unsigned int decodePolyIdPoly(dtPolyRef ref) const { +#ifdef DT_POLYREF64 + const dtPolyRef polyMask = ((dtPolyRef)1< 0] + /// @param[in] options Query options. (see: #dtStraightPathOptions) /// @returns The status flags for the query. dtStatus findStraightPath(const float* startPos, const float* endPos, const dtPolyRef* path, const int pathSize, float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, - int* straightPathCount, const int maxStraightPath) const; + int* straightPathCount, const int maxStraightPath, const int options = 0) const; ///@} /// @name Sliced Pathfinding Functions @@ -178,10 +214,11 @@ class dtNavMeshQuery /// @param[in] startPos A position within the start polygon. [(x, y, z)] /// @param[in] endPos A position within the end polygon. [(x, y, z)] /// @param[in] filter The polygon filter to apply to the query. + /// @param[in] options query options (see: #dtFindPathOptions) /// @returns The status flags for the query. dtStatus initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef, const float* startPos, const float* endPos, - const dtQueryFilter* filter); + const dtQueryFilter* filter, const unsigned int options = 0); /// Updates an in-progress sliced path query. /// @param[in] maxIter The maximum number of iterations to perform. @@ -199,8 +236,8 @@ class dtNavMeshQuery /// Finalizes and returns the results of an incomplete sliced path query, returning the path to the furthest /// polygon on the existing path that was visited during the search. - /// @param[out] existing An array of polygon references for the existing path. - /// @param[out] existingSize The number of polygon in the @p existing array. + /// @param[in] existing An array of polygon references for the existing path. + /// @param[in] existingSize The number of polygon in the @p existing array. /// @param[out] path An ordered list of polygon references representing the path. (Start to end.) /// [(polyRef) * @p pathCount] /// @param[out] pathCount The number of polygons returned in the @p path array. @@ -307,6 +344,7 @@ class dtNavMeshQuery /// Casts a 'walkability' ray along the surface of the navigation mesh from /// the start position toward the end position. + /// @note A wrapper around raycast(..., RaycastHit*). Retained for backward compatibility. /// @param[in] startRef The reference id of the start polygon. /// @param[in] startPos A position within the start polygon representing /// the start of the ray. [(x, y, z)] @@ -322,6 +360,22 @@ class dtNavMeshQuery const dtQueryFilter* filter, float* t, float* hitNormal, dtPolyRef* path, int* pathCount, const int maxPath) const; + /// Casts a 'walkability' ray along the surface of the navigation mesh from + /// the start position toward the end position. + /// @param[in] startRef The reference id of the start polygon. + /// @param[in] startPos A position within the start polygon representing + /// the start of the ray. [(x, y, z)] + /// @param[in] endPos The position to cast the ray toward. [(x, y, z)] + /// @param[in] filter The polygon filter to apply to the query. + /// @param[in] flags govern how the raycast behaves. See dtRaycastOptions + /// @param[out] hit Pointer to a raycast hit structure which will be filled by the results. + /// @param[in] prevRef parent of start ref. Used during for cost calculation [opt] + /// @returns The status flags for the query. + dtStatus raycast(dtPolyRef startRef, const float* startPos, const float* endPos, + const dtQueryFilter* filter, const unsigned int options, + dtRaycastHit* hit, dtPolyRef prevRef = 0) const; + + /// Finds the distance from the specified position to the nearest polygon wall. /// @param[in] startRef The reference id of the polygon containing @p centerPos. /// @param[in] centerPos The center of the search circle. [(x, y, z)] @@ -377,8 +431,9 @@ class dtNavMeshQuery /// @param[in] ref The reference id of the polygon. /// @param[in] pos The position to check. [(x, y, z)] /// @param[out] closest The closest point on the polygon. [(x, y, z)] + /// @param[out] posOverPoly True of the position is over the polygon. /// @returns The status flags for the query. - dtStatus closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest) const; + dtStatus closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const; /// Returns a point on the boundary closest to the source point if the source point is outside the /// polygon's xz-bounds. @@ -420,6 +475,9 @@ class dtNavMeshQuery /// @} private: + // Explicitly disabled copy constructor and copy assignment operator + dtNavMeshQuery(const dtNavMeshQuery&); + dtNavMeshQuery& operator=(const dtNavMeshQuery&); /// Returns neighbour tile based on side. dtMeshTile* getNeighbourTileAt(int x, int y, int side) const; @@ -427,12 +485,7 @@ class dtNavMeshQuery /// Queries polygons within a tile. int queryPolygonsInTile(const dtMeshTile* tile, const float* qmin, const float* qmax, const dtQueryFilter* filter, dtPolyRef* polys, const int maxPolys) const; - /// Find nearest polygon within a tile. - dtPolyRef findNearestPolyInTile(const dtMeshTile* tile, const float* center, const float* extents, - const dtQueryFilter* filter, float* nearestPt) const; - /// Returns closest point on polygon. - void closestPointOnPolyInTile(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* closest) const; - + /// Returns portal points between two polygons. dtStatus getPortalPoints(dtPolyRef from, dtPolyRef to, float* left, float* right, unsigned char& fromType, unsigned char& toType) const; @@ -446,6 +499,16 @@ class dtNavMeshQuery dtPolyRef to, const dtPoly* toPoly, const dtMeshTile* toTile, float* mid) const; + // Appends vertex to a straight path + dtStatus appendVertex(const float* pos, const unsigned char flags, const dtPolyRef ref, + float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, + int* straightPathCount, const int maxStraightPath) const; + + // Appends intermediate portal points to a straight path. + dtStatus appendPortals(const int startIdx, const int endIdx, const float* endPos, const dtPolyRef* path, + float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, + int* straightPathCount, const int maxStraightPath, const int options) const; + const dtNavMesh* m_nav; ///< Pointer to navmesh data. struct dtQueryData @@ -456,6 +519,8 @@ class dtNavMeshQuery dtPolyRef startRef, endRef; float startPos[3], endPos[3]; const dtQueryFilter* filter; + unsigned int options; + float raycastLimitSqr; }; dtQueryData m_query; ///< Sliced query state. diff --git a/Engine/lib/recast/Detour/Include/DetourNode.h b/Engine/lib/recast/Detour/Include/DetourNode.h index b68c922d03..db09747080 100644 --- a/Engine/lib/recast/Detour/Include/DetourNode.h +++ b/Engine/lib/recast/Detour/Include/DetourNode.h @@ -25,48 +25,56 @@ enum dtNodeFlags { DT_NODE_OPEN = 0x01, DT_NODE_CLOSED = 0x02, + DT_NODE_PARENT_DETACHED = 0x04, // parent of the node is not adjacent. Found using raycast. }; typedef unsigned short dtNodeIndex; static const dtNodeIndex DT_NULL_IDX = (dtNodeIndex)~0; +static const int DT_NODE_PARENT_BITS = 24; +static const int DT_NODE_STATE_BITS = 2; struct dtNode { - float pos[3]; ///< Position of the node. - float cost; ///< Cost from previous node to current node. - float total; ///< Cost up to the node. - unsigned int pidx : 30; ///< Index to parent node. - unsigned int flags : 2; ///< Node flags 0/open/closed. - dtPolyRef id; ///< Polygon ref the node corresponds to. + float pos[3]; ///< Position of the node. + float cost; ///< Cost from previous node to current node. + float total; ///< Cost up to the node. + unsigned int pidx : DT_NODE_PARENT_BITS; ///< Index to parent node. + unsigned int state : DT_NODE_STATE_BITS; ///< extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE + unsigned int flags : 3; ///< Node flags. A combination of dtNodeFlags. + dtPolyRef id; ///< Polygon ref the node corresponds to. }; +static const int DT_MAX_STATES_PER_NODE = 1 << DT_NODE_STATE_BITS; // number of extra states per node. See dtNode::state class dtNodePool { public: dtNodePool(int maxNodes, int hashSize); ~dtNodePool(); - inline void operator=(const dtNodePool&) {} void clear(); - dtNode* getNode(dtPolyRef id); - dtNode* findNode(dtPolyRef id); + + // Get a dtNode by ref and extra state information. If there is none then - allocate + // There can be more than one node for the same polyRef but with different extra state information + dtNode* getNode(dtPolyRef id, unsigned char state=0); + dtNode* findNode(dtPolyRef id, unsigned char state); + unsigned int findNodes(dtPolyRef id, dtNode** nodes, const int maxNodes); inline unsigned int getNodeIdx(const dtNode* node) const { if (!node) return 0; - return (unsigned int)(node - m_nodes)+1; + return (unsigned int)(node - m_nodes) + 1; } inline dtNode* getNodeAtIdx(unsigned int idx) { if (!idx) return 0; - return &m_nodes[idx-1]; + return &m_nodes[idx - 1]; } inline const dtNode* getNodeAtIdx(unsigned int idx) const { if (!idx) return 0; - return &m_nodes[idx-1]; + return &m_nodes[idx - 1]; } inline int getMemUsed() const @@ -82,8 +90,12 @@ class dtNodePool inline int getHashSize() const { return m_hashSize; } inline dtNodeIndex getFirst(int bucket) const { return m_first[bucket]; } inline dtNodeIndex getNext(int i) const { return m_next[i]; } + inline int getNodeCount() const { return m_nodeCount; } private: + // Explicitly disabled copy constructor and copy assignment operator. + dtNodePool(const dtNodePool&); + dtNodePool& operator=(const dtNodePool&); dtNode* m_nodes; dtNodeIndex* m_first; @@ -98,17 +110,10 @@ class dtNodeQueue public: dtNodeQueue(int n); ~dtNodeQueue(); - inline void operator=(dtNodeQueue&) {} - inline void clear() - { - m_size = 0; - } + inline void clear() { m_size = 0; } - inline dtNode* top() - { - return m_heap[0]; - } + inline dtNode* top() { return m_heap[0]; } inline dtNode* pop() { @@ -141,12 +146,16 @@ class dtNodeQueue inline int getMemUsed() const { return sizeof(*this) + - sizeof(dtNode*)*(m_capacity+1); + sizeof(dtNode*) * (m_capacity + 1); } inline int getCapacity() const { return m_capacity; } private: + // Explicitly disabled copy constructor and copy assignment operator. + dtNodeQueue(const dtNodeQueue&); + dtNodeQueue& operator=(const dtNodeQueue&); + void bubbleUp(int i, dtNode* node); void trickleDown(int i, dtNode* node); diff --git a/Engine/lib/recast/Detour/Source/DetourAlloc.cpp b/Engine/lib/recast/Detour/Source/DetourAlloc.cpp index 5f671df5bd..d9ad1fc013 100644 --- a/Engine/lib/recast/Detour/Source/DetourAlloc.cpp +++ b/Engine/lib/recast/Detour/Source/DetourAlloc.cpp @@ -19,7 +19,7 @@ #include #include "DetourAlloc.h" -static void *dtAllocDefault(int size, dtAllocHint) +static void *dtAllocDefault(size_t size, dtAllocHint) { return malloc(size); } @@ -38,7 +38,7 @@ void dtAllocSetCustom(dtAllocFunc *allocFunc, dtFreeFunc *freeFunc) sFreeFunc = freeFunc ? freeFunc : dtFreeDefault; } -void* dtAlloc(int size, dtAllocHint hint) +void* dtAlloc(size_t size, dtAllocHint hint) { return sAllocFunc(size, hint); } diff --git a/Engine/lib/recast/Detour/Source/DetourCommon.cpp b/Engine/lib/recast/Detour/Source/DetourCommon.cpp index e003bf60cc..26fe65c178 100644 --- a/Engine/lib/recast/Detour/Source/DetourCommon.cpp +++ b/Engine/lib/recast/Detour/Source/DetourCommon.cpp @@ -16,16 +16,11 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include #include "DetourCommon.h" +#include "DetourMath.h" ////////////////////////////////////////////////////////////////////////////////////////// -float dtSqrt(float x) -{ - return sqrtf(x); -} - void dtClosestPtPointTriangle(float* closest, const float* p, const float* a, const float* b, const float* c) { @@ -360,7 +355,7 @@ void dtRandomPointInConvexPoly(const float* pts, const int npts, float* areas, acc += dacc; } - float v = dtSqrt(t); + float v = dtMathSqrtf(t); const float a = 1 - v; const float b = (1 - u) * v; @@ -374,3 +369,20 @@ void dtRandomPointInConvexPoly(const float* pts, const int npts, float* areas, out[2] = a*pa[2] + b*pb[2] + c*pc[2]; } +inline float vperpXZ(const float* a, const float* b) { return a[0]*b[2] - a[2]*b[0]; } + +bool dtIntersectSegSeg2D(const float* ap, const float* aq, + const float* bp, const float* bq, + float& s, float& t) +{ + float u[3], v[3], w[3]; + dtVsub(u,aq,ap); + dtVsub(v,bq,bp); + dtVsub(w,ap,bp); + float d = vperpXZ(u,v); + if (fabsf(d) < 1e-6f) return false; + s = vperpXZ(v,w) / d; + t = vperpXZ(u,w) / d; + return true; +} + diff --git a/Engine/lib/recast/Detour/Source/DetourNavMesh.cpp b/Engine/lib/recast/Detour/Source/DetourNavMesh.cpp index a4bd38c9ae..bb6c9e4b7f 100644 --- a/Engine/lib/recast/Detour/Source/DetourNavMesh.cpp +++ b/Engine/lib/recast/Detour/Source/DetourNavMesh.cpp @@ -16,13 +16,13 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include #include #include #include #include "DetourNavMesh.h" #include "DetourNode.h" #include "DetourCommon.h" +#include "DetourMath.h" #include "DetourAlloc.h" #include "DetourAssert.h" #include @@ -193,11 +193,13 @@ dtNavMesh::dtNavMesh() : m_tileLutMask(0), m_posLookup(0), m_nextFree(0), - m_tiles(0), - m_saltBits(0), - m_tileBits(0), - m_polyBits(0) + m_tiles(0) { +#ifndef DT_POLYREF64 + m_saltBits = 0; + m_tileBits = 0; + m_polyBits = 0; +#endif memset(&m_params, 0, sizeof(dtNavMeshParams)); m_orig[0] = 0; m_orig[1] = 0; @@ -249,12 +251,15 @@ dtStatus dtNavMesh::init(const dtNavMeshParams* params) } // Init ID generator values. +#ifndef DT_POLYREF64 m_tileBits = dtIlog2(dtNextPow2((unsigned int)params->maxTiles)); m_polyBits = dtIlog2(dtNextPow2((unsigned int)params->maxPolys)); // Only allow 31 salt bits, since the salt mask is calculated using 32bit uint and it will overflow. m_saltBits = dtMin((unsigned int)31, 32 - m_tileBits - m_polyBits); + if (m_saltBits < 10) return DT_FAILURE | DT_INVALID_PARAM; +#endif return DT_SUCCESS; } @@ -345,7 +350,7 @@ int dtNavMesh::findConnectingPolys(const float* va, const float* vb, return n; } -void dtNavMesh::unconnectExtLinks(dtMeshTile* tile, dtMeshTile* target) +void dtNavMesh::unconnectLinks(dtMeshTile* tile, dtMeshTile* target) { if (!tile || !target) return; @@ -358,10 +363,9 @@ void dtNavMesh::unconnectExtLinks(dtMeshTile* tile, dtMeshTile* target) unsigned int pj = DT_NULL_LINK; while (j != DT_NULL_LINK) { - if (tile->links[j].side != 0xff && - decodePolyIdTile(tile->links[j].ref) == targetNum) + if (decodePolyIdTile(tile->links[j].ref) == targetNum) { - // Revove link. + // Remove link. unsigned int nj = tile->links[j].next; if (pj == DT_NULL_LINK) poly->firstLink = nj; @@ -612,14 +616,65 @@ void dtNavMesh::baseOffMeshLinks(dtMeshTile* tile) } } -void dtNavMesh::closestPointOnPolyInTile(const dtMeshTile* tile, unsigned int ip, - const float* pos, float* closest) const +void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const { - const dtPoly* poly = &tile->polys[ip]; + const dtMeshTile* tile = 0; + const dtPoly* poly = 0; + getTileAndPolyByRefUnsafe(ref, &tile, &poly); - float closestDistSqr = FLT_MAX; + // Off-mesh connections don't have detail polygons. + if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) + { + const float* v0 = &tile->verts[poly->verts[0]*3]; + const float* v1 = &tile->verts[poly->verts[1]*3]; + const float d0 = dtVdist(pos, v0); + const float d1 = dtVdist(pos, v1); + const float u = d0 / (d0+d1); + dtVlerp(closest, v0, v1, u); + if (posOverPoly) + *posOverPoly = false; + return; + } + + const unsigned int ip = (unsigned int)(poly - tile->polys); const dtPolyDetail* pd = &tile->detailMeshes[ip]; + // Clamp point to be inside the polygon. + float verts[DT_VERTS_PER_POLYGON*3]; + float edged[DT_VERTS_PER_POLYGON]; + float edget[DT_VERTS_PER_POLYGON]; + const int nv = poly->vertCount; + for (int i = 0; i < nv; ++i) + dtVcopy(&verts[i*3], &tile->verts[poly->verts[i]*3]); + + dtVcopy(closest, pos); + if (!dtDistancePtPolyEdgesSqr(pos, verts, nv, edged, edget)) + { + // Point is outside the polygon, dtClamp to nearest edge. + float dmin = FLT_MAX; + int imin = -1; + for (int i = 0; i < nv; ++i) + { + if (edged[i] < dmin) + { + dmin = edged[i]; + imin = i; + } + } + const float* va = &verts[imin*3]; + const float* vb = &verts[((imin+1)%nv)*3]; + dtVlerp(closest, va, vb, edget[imin]); + + if (posOverPoly) + *posOverPoly = false; + } + else + { + if (posOverPoly) + *posOverPoly = true; + } + + // Find height at the location. for (int j = 0; j < pd->triCount; ++j) { const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; @@ -631,13 +686,11 @@ void dtNavMesh::closestPointOnPolyInTile(const dtMeshTile* tile, unsigned int ip else v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3]; } - float pt[3]; - dtClosestPtPointTriangle(pt, pos, v[0], v[1], v[2]); - float d = dtVdistSqr(pos, pt); - if (d < closestDistSqr) + float h; + if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h)) { - dtVcopy(closest, pt); - closestDistSqr = d; + closest[1] = h; + break; } } } @@ -661,12 +714,27 @@ dtPolyRef dtNavMesh::findNearestPolyInTile(const dtMeshTile* tile, { dtPolyRef ref = polys[i]; float closestPtPoly[3]; - closestPointOnPolyInTile(tile, decodePolyIdPoly(ref), center, closestPtPoly); - float d = dtVdistSqr(center, closestPtPoly); + float diff[3]; + bool posOverPoly = false; + float d; + closestPointOnPoly(ref, center, closestPtPoly, &posOverPoly); + + // If a point is directly over a polygon and closer than + // climb height, favor that instead of straight line nearest point. + dtVsub(diff, center, closestPtPoly); + if (posOverPoly) + { + d = dtAbs(diff[1]) - tile->header->walkableClimb; + d = d > 0 ? d*d : 0; + } + else + { + d = dtVlenSqr(diff); + } + if (d < nearestDistanceSqr) { - if (nearestPt) - dtVcopy(nearestPt, closestPtPoly); + dtVcopy(nearestPt, closestPtPoly); nearestDistanceSqr = d; nearest = ref; } @@ -769,6 +837,11 @@ int dtNavMesh::queryPolygonsInTile(const dtMeshTile* tile, const float* qmin, co /// tile will be restored to the same values they were before the tile was /// removed. /// +/// The nav mesh assumes exclusive access to the data passed and will make +/// changes to the dynamic portion of the data. For that reason the data +/// should not be reused in other nav meshes until the tile has been successfully +/// removed from this nav mesh. +/// /// @see dtCreateNavMeshData, #removeTile dtStatus dtNavMesh::addTile(unsigned char* data, int dataSize, int flags, dtTileRef lastRef, dtTileRef* result) @@ -1123,25 +1196,24 @@ dtStatus dtNavMesh::removeTile(dtTileRef ref, unsigned char** data, int* dataSiz } // Remove connections to neighbour tiles. - // Create connections with neighbour tiles. static const int MAX_NEIS = 32; dtMeshTile* neis[MAX_NEIS]; int nneis; - // Connect with layers in current tile. + // Disconnect from other layers in current tile. nneis = getTilesAt(tile->header->x, tile->header->y, neis, MAX_NEIS); for (int j = 0; j < nneis; ++j) { if (neis[j] == tile) continue; - unconnectExtLinks(neis[j], tile); + unconnectLinks(neis[j], tile); } - // Connect with neighbour tiles. + // Disconnect from neighbour tiles. for (int i = 0; i < 8; ++i) { nneis = getNeighbourTilesAt(tile->header->x, tile->header->y, i, neis, MAX_NEIS); for (int j = 0; j < nneis; ++j) - unconnectExtLinks(neis[j], tile); + unconnectLinks(neis[j], tile); } // Reset tile. @@ -1173,7 +1245,11 @@ dtStatus dtNavMesh::removeTile(dtTileRef ref, unsigned char** data, int* dataSiz tile->offMeshCons = 0; // Update salt, salt should never be zero. +#ifdef DT_POLYREF64 + tile->salt = (tile->salt+1) & ((1<salt = (tile->salt+1) & ((1<salt == 0) tile->salt++; diff --git a/Engine/lib/recast/Detour/Source/DetourNavMeshBuilder.cpp b/Engine/lib/recast/Detour/Source/DetourNavMeshBuilder.cpp index 9d8471b96a..1bf271bed7 100644 --- a/Engine/lib/recast/Detour/Source/DetourNavMeshBuilder.cpp +++ b/Engine/lib/recast/Detour/Source/DetourNavMeshBuilder.cpp @@ -16,13 +16,13 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include #include #include #include #include #include "DetourNavMesh.h" #include "DetourCommon.h" +#include "DetourMath.h" #include "DetourNavMeshBuilder.h" #include "DetourAlloc.h" #include "DetourAssert.h" @@ -202,8 +202,8 @@ static int createBVTree(const unsigned short* verts, const int /*nverts*/, if (z > it.bmax[2]) it.bmax[2] = z; } // Remap y - it.bmin[1] = (unsigned short)floorf((float)it.bmin[1]*ch/cs); - it.bmax[1] = (unsigned short)ceilf((float)it.bmax[1]*ch/cs); + it.bmin[1] = (unsigned short)dtMathFloorf((float)it.bmin[1]*ch/cs); + it.bmax[1] = (unsigned short)dtMathCeilf((float)it.bmax[1]*ch/cs); } int curNode = 0; diff --git a/Engine/lib/recast/Detour/Source/DetourNavMeshQuery.cpp b/Engine/lib/recast/Detour/Source/DetourNavMeshQuery.cpp index 0eb0011460..87706c1de3 100644 --- a/Engine/lib/recast/Detour/Source/DetourNavMeshQuery.cpp +++ b/Engine/lib/recast/Detour/Source/DetourNavMeshQuery.cpp @@ -16,13 +16,13 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include #include #include #include "DetourNavMeshQuery.h" #include "DetourNavMesh.h" #include "DetourNode.h" #include "DetourCommon.h" +#include "DetourMath.h" #include "DetourAlloc.h" #include "DetourAssert.h" #include @@ -165,6 +165,9 @@ dtNavMeshQuery::~dtNavMeshQuery() /// This function can be used multiple times. dtStatus dtNavMeshQuery::init(const dtNavMesh* nav, const int maxNodes) { + if (maxNodes > DT_NULL_IDX || maxNodes > (1 << DT_NODE_PARENT_BITS) - 1) + return DT_FAILURE | DT_INVALID_PARAM; + m_nav = nav; if (!m_nodePool || m_nodePool->getMaxNodes() < maxNodes) @@ -195,7 +198,6 @@ dtStatus dtNavMeshQuery::init(const dtNavMesh* nav, const int maxNodes) m_tinyNodePool->clear(); } - // TODO: check the open list size too. if (!m_openList || m_openList->getCapacity() < maxNodes) { if (m_openList) @@ -308,7 +310,7 @@ dtStatus dtNavMeshQuery::findRandomPoint(const dtQueryFilter* filter, float (*fr return DT_SUCCESS; } -dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const float* centerPos, const float radius, +dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const float* centerPos, const float maxRadius, const dtQueryFilter* filter, float (*frand)(), dtPolyRef* randomRef, float* randomPt) const { @@ -340,7 +342,7 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f dtStatus status = DT_SUCCESS; - const float radiusSqr = dtSqr(radius); + const float radiusSqr = dtSqr(maxRadius); float areaSum = 0.0f; const dtMeshTile* randomTile = 0; @@ -501,7 +503,7 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f /// /// See closestPointOnPolyBoundary() for a limited but faster option. /// -dtStatus dtNavMeshQuery::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest) const +dtStatus dtNavMeshQuery::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const { dtAssert(m_nav); const dtMeshTile* tile = 0; @@ -511,14 +513,6 @@ dtStatus dtNavMeshQuery::closestPointOnPoly(dtPolyRef ref, const float* pos, flo if (!tile) return DT_FAILURE | DT_INVALID_PARAM; - closestPointOnPolyInTile(tile, poly, pos, closest); - - return DT_SUCCESS; -} - -void dtNavMeshQuery::closestPointOnPolyInTile(const dtMeshTile* tile, const dtPoly* poly, - const float* pos, float* closest) const -{ // Off-mesh connections don't have detail polygons. if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) { @@ -528,15 +522,14 @@ void dtNavMeshQuery::closestPointOnPolyInTile(const dtMeshTile* tile, const dtPo const float d1 = dtVdist(pos, v1); const float u = d0 / (d0+d1); dtVlerp(closest, v0, v1, u); - return; + if (posOverPoly) + *posOverPoly = false; + return DT_SUCCESS; } const unsigned int ip = (unsigned int)(poly - tile->polys); const dtPolyDetail* pd = &tile->detailMeshes[ip]; - // TODO: The commented out version finds 'cylinder distance' instead of 'sphere distance' to the navmesh. - // Test and enable. -/* // Clamp point to be inside the polygon. float verts[DT_VERTS_PER_POLYGON*3]; float edged[DT_VERTS_PER_POLYGON]; @@ -562,6 +555,14 @@ void dtNavMeshQuery::closestPointOnPolyInTile(const dtMeshTile* tile, const dtPo const float* va = &verts[imin*3]; const float* vb = &verts[((imin+1)%nv)*3]; dtVlerp(closest, va, vb, edget[imin]); + + if (posOverPoly) + *posOverPoly = false; + } + else + { + if (posOverPoly) + *posOverPoly = true; } // Find height at the location. @@ -583,30 +584,8 @@ void dtNavMeshQuery::closestPointOnPolyInTile(const dtMeshTile* tile, const dtPo break; } } -*/ - float closestDistSqr = FLT_MAX; - for (int j = 0; j < pd->triCount; ++j) - { - const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; - const float* v[3]; - for (int k = 0; k < 3; ++k) - { - if (t[k] < poly->vertCount) - v[k] = &tile->verts[poly->verts[t[k]]*3]; - else - v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3]; - } - - float pt[3]; - dtClosestPtPointTriangle(pt, pos, v[0], v[1], v[2]); - float d = dtVdistSqr(pos, pt); - - if (d < closestDistSqr) - { - dtVcopy(closest, pt); - closestDistSqr = d; - } - } + + return DT_SUCCESS; } /// @par @@ -685,8 +664,8 @@ dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* h { const float* v0 = &tile->verts[poly->verts[0]*3]; const float* v1 = &tile->verts[poly->verts[1]*3]; - const float d0 = dtVdist(pos, v0); - const float d1 = dtVdist(pos, v1); + const float d0 = dtVdist2D(pos, v0); + const float d1 = dtVdist2D(pos, v1); const float u = d0 / (d0+d1); if (height) *height = v0[1] + (v1[1] - v0[1]) * u; @@ -727,7 +706,7 @@ dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* h /// @p nearestRef before using @p nearestPt. /// /// @warning This function is not suitable for large area searches. If the search -/// extents overlaps more than 128 polygons it may return an invalid result. +/// extents overlaps more than MAX_SEARCH (128) polygons it may return an invalid result. /// dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* extents, const dtQueryFilter* filter, @@ -735,72 +714,66 @@ dtStatus dtNavMeshQuery::findNearestPoly(const float* center, const float* exten { dtAssert(m_nav); - *nearestRef = 0; + if (!nearestRef) + return DT_FAILURE | DT_INVALID_PARAM; // Get nearby polygons from proximity grid. - dtPolyRef polys[128]; + const int MAX_SEARCH = 128; + dtPolyRef polys[MAX_SEARCH]; int polyCount = 0; - if (dtStatusFailed(queryPolygons(center, extents, filter, polys, &polyCount, 128))) + if (dtStatusFailed(queryPolygons(center, extents, filter, polys, &polyCount, MAX_SEARCH))) return DT_FAILURE | DT_INVALID_PARAM; - // Find nearest polygon amongst the nearby polygons. - dtPolyRef nearest = 0; - float nearestDistanceSqr = FLT_MAX; - for (int i = 0; i < polyCount; ++i) - { - dtPolyRef ref = polys[i]; - float closestPtPoly[3]; - closestPointOnPoly(ref, center, closestPtPoly); - float d = dtVdistSqr(center, closestPtPoly); - if (d < nearestDistanceSqr) - { - if (nearestPt) - dtVcopy(nearestPt, closestPtPoly); - nearestDistanceSqr = d; - nearest = ref; - } - } - - if (nearestRef) - *nearestRef = nearest; - - return DT_SUCCESS; -} + *nearestRef = 0; -dtPolyRef dtNavMeshQuery::findNearestPolyInTile(const dtMeshTile* tile, const float* center, const float* extents, - const dtQueryFilter* filter, float* nearestPt) const -{ - dtAssert(m_nav); - - float bmin[3], bmax[3]; - dtVsub(bmin, center, extents); - dtVadd(bmax, center, extents); - - // Get nearby polygons from proximity grid. - dtPolyRef polys[128]; - int polyCount = queryPolygonsInTile(tile, bmin, bmax, filter, polys, 128); + if (polyCount == 0) + return DT_SUCCESS; // Find nearest polygon amongst the nearby polygons. dtPolyRef nearest = 0; + float nearestPoint[3]; + float nearestDistanceSqr = FLT_MAX; for (int i = 0; i < polyCount; ++i) { dtPolyRef ref = polys[i]; - const dtPoly* poly = &tile->polys[m_nav->decodePolyIdPoly(ref)]; float closestPtPoly[3]; - closestPointOnPolyInTile(tile, poly, center, closestPtPoly); - - float d = dtVdistSqr(center, closestPtPoly); + float diff[3]; + bool posOverPoly = false; + float d = 0; + closestPointOnPoly(ref, center, closestPtPoly, &posOverPoly); + + // If a point is directly over a polygon and closer than + // climb height, favor that instead of straight line nearest point. + dtVsub(diff, center, closestPtPoly); + if (posOverPoly) + { + const dtMeshTile* tile = 0; + const dtPoly* poly = 0; + m_nav->getTileAndPolyByRefUnsafe(polys[i], &tile, &poly); + d = dtAbs(diff[1]) - tile->header->walkableClimb; + d = d > 0 ? d*d : 0; + } + else + { + d = dtVlenSqr(diff); + } + if (d < nearestDistanceSqr) { - if (nearestPt) - dtVcopy(nearestPt, closestPtPoly); + dtVcopy(nearestPoint, closestPtPoly); + nearestDistanceSqr = d; nearest = ref; } } - return nearest; + *nearestRef = nearest; + + if (nearestPt) + dtVcopy(nearestPt, nearestPoint); + + return DT_SUCCESS; } int dtNavMeshQuery::queryPolygonsInTile(const dtMeshTile* tile, const float* qmin, const float* qmax, @@ -1050,7 +1023,13 @@ dtStatus dtNavMeshQuery::findPath(dtPolyRef startRef, dtPolyRef endRef, if (!filter->passFilter(neighbourRef, neighbourTile, neighbourPoly)) continue; - dtNode* neighbourNode = m_nodePool->getNode(neighbourRef); + // deal explicitly with crossing tile boundaries + unsigned char crossSide = 0; + if (bestTile->links[i].side != 0xff) + crossSide = bestTile->links[i].side >> 1; + + // get the node + dtNode* neighbourNode = m_nodePool->getNode(neighbourRef, crossSide); if (!neighbourNode) { status |= DT_OUT_OF_NODES; @@ -1178,7 +1157,7 @@ dtStatus dtNavMeshQuery::findPath(dtPolyRef startRef, dtPolyRef endRef, /// dtStatus dtNavMeshQuery::initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef, const float* startPos, const float* endPos, - const dtQueryFilter* filter) + const dtQueryFilter* filter, const unsigned int options) { dtAssert(m_nav); dtAssert(m_nodePool); @@ -1192,6 +1171,8 @@ dtStatus dtNavMeshQuery::initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef dtVcopy(m_query.startPos, startPos); dtVcopy(m_query.endPos, endPos); m_query.filter = filter; + m_query.options = options; + m_query.raycastLimitSqr = FLT_MAX; if (!startRef || !endRef) return DT_FAILURE | DT_INVALID_PARAM; @@ -1200,6 +1181,16 @@ dtStatus dtNavMeshQuery::initSlicedFindPath(dtPolyRef startRef, dtPolyRef endRef if (!m_nav->isValidPolyRef(startRef) || !m_nav->isValidPolyRef(endRef)) return DT_FAILURE | DT_INVALID_PARAM; + // trade quality with performance? + if (options & DT_FINDPATH_ANY_ANGLE) + { + // limiting to several times the character radius yields nice results. It is not sensitive + // so it is enough to compute it from the first tile. + const dtMeshTile* tile = m_nav->getTileByRef(startRef); + float agentRadius = tile->header->walkableRadius; + m_query.raycastLimitSqr = dtSqr(agentRadius * DT_RAY_CAST_LIMIT_PROPORTIONS); + } + if (startRef == endRef) { m_query.status = DT_SUCCESS; @@ -1236,6 +1227,9 @@ dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters) m_query.status = DT_FAILURE; return DT_FAILURE; } + + dtRaycastHit rayHit; + rayHit.maxPath = 0; int iter = 0; while (iter < maxIter && !m_openList->empty()) @@ -1272,15 +1266,22 @@ dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters) return m_query.status; } - // Get parent poly and tile. - dtPolyRef parentRef = 0; + // Get parent and grand parent poly and tile. + dtPolyRef parentRef = 0, grandpaRef = 0; const dtMeshTile* parentTile = 0; const dtPoly* parentPoly = 0; + dtNode* parentNode = 0; if (bestNode->pidx) - parentRef = m_nodePool->getNodeAtIdx(bestNode->pidx)->id; + { + parentNode = m_nodePool->getNodeAtIdx(bestNode->pidx); + parentRef = parentNode->id; + if (parentNode->pidx) + grandpaRef = m_nodePool->getNodeAtIdx(parentNode->pidx)->id; + } if (parentRef) { - if (dtStatusFailed(m_nav->getTileAndPolyByRef(parentRef, &parentTile, &parentPoly))) + bool invalidParent = dtStatusFailed(m_nav->getTileAndPolyByRef(parentRef, &parentTile, &parentPoly)); + if (invalidParent || (grandpaRef && !m_nav->isValidPolyRef(grandpaRef)) ) { // The polygon has disappeared during the sliced query, fail. m_query.status = DT_FAILURE; @@ -1289,6 +1290,14 @@ dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters) return m_query.status; } } + + // decide whether to test raycast to previous nodes + bool tryLOS = false; + if (m_query.options & DT_FINDPATH_ANY_ANGLE) + { + if ((parentRef != 0) && (dtVdistSqr(parentNode->pos, bestNode->pos) < m_query.raycastLimitSqr)) + tryLOS = true; + } for (unsigned int i = bestPoly->firstLink; i != DT_NULL_LINK; i = bestTile->links[i].next) { @@ -1307,13 +1316,18 @@ dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters) if (!m_query.filter->passFilter(neighbourRef, neighbourTile, neighbourPoly)) continue; - dtNode* neighbourNode = m_nodePool->getNode(neighbourRef); + // get the neighbor node + dtNode* neighbourNode = m_nodePool->getNode(neighbourRef, 0); if (!neighbourNode) { m_query.status |= DT_OUT_OF_NODES; continue; } + // do not expand to nodes that were already visited from the same parent + if (neighbourNode->pidx != 0 && neighbourNode->pidx == bestNode->pidx) + continue; + // If the node is visited the first time, calculate node position. if (neighbourNode->flags == 0) { @@ -1326,30 +1340,44 @@ dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters) float cost = 0; float heuristic = 0; - // Special case for last node. - if (neighbourRef == m_query.endRef) + // raycast parent + bool foundShortCut = false; + rayHit.pathCost = rayHit.t = 0; + if (tryLOS) { - // Cost + raycast(parentRef, parentNode->pos, neighbourNode->pos, m_query.filter, DT_RAYCAST_USE_COSTS, &rayHit, grandpaRef); + foundShortCut = rayHit.t >= 1.0f; + } + + // update move cost + if (foundShortCut) + { + // shortcut found using raycast. Using shorter cost instead + cost = parentNode->cost + rayHit.pathCost; + } + else + { + // No shortcut found. const float curCost = m_query.filter->getCost(bestNode->pos, neighbourNode->pos, parentRef, parentTile, parentPoly, - bestRef, bestTile, bestPoly, - neighbourRef, neighbourTile, neighbourPoly); + bestRef, bestTile, bestPoly, + neighbourRef, neighbourTile, neighbourPoly); + cost = bestNode->cost + curCost; + } + + // Special case for last node. + if (neighbourRef == m_query.endRef) + { const float endCost = m_query.filter->getCost(neighbourNode->pos, m_query.endPos, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly, 0, 0, 0); - cost = bestNode->cost + curCost + endCost; + cost = cost + endCost; heuristic = 0; } else { - // Cost - const float curCost = m_query.filter->getCost(bestNode->pos, neighbourNode->pos, - parentRef, parentTile, parentPoly, - bestRef, bestTile, bestPoly, - neighbourRef, neighbourTile, neighbourPoly); - cost = bestNode->cost + curCost; heuristic = dtVdist(neighbourNode->pos, m_query.endPos)*H_SCALE; } @@ -1363,11 +1391,13 @@ dtStatus dtNavMeshQuery::updateSlicedFindPath(const int maxIter, int* doneIters) continue; // Add or update the node. - neighbourNode->pidx = m_nodePool->getNodeIdx(bestNode); + neighbourNode->pidx = foundShortCut ? bestNode->pidx : m_nodePool->getNodeIdx(bestNode); neighbourNode->id = neighbourRef; - neighbourNode->flags = (neighbourNode->flags & ~DT_NODE_CLOSED); + neighbourNode->flags = (neighbourNode->flags & ~(DT_NODE_CLOSED | DT_NODE_PARENT_DETACHED)); neighbourNode->cost = cost; neighbourNode->total = total; + if (foundShortCut) + neighbourNode->flags = (neighbourNode->flags | DT_NODE_PARENT_DETACHED); if (neighbourNode->flags & DT_NODE_OPEN) { @@ -1431,11 +1461,15 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, dtNode* prev = 0; dtNode* node = m_query.lastBestNode; + int prevRay = 0; do { dtNode* next = m_nodePool->getNodeAtIdx(node->pidx); node->pidx = m_nodePool->getNodeIdx(prev); prev = node; + int nextRay = node->flags & DT_NODE_PARENT_DETACHED; // keep track of whether parent is not adjacent (i.e. due to raycast shortcut) + node->flags = (node->flags & ~DT_NODE_PARENT_DETACHED) | prevRay; // and store it in the reversed path's node + prevRay = nextRay; node = next; } while (node); @@ -1444,13 +1478,31 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPath(dtPolyRef* path, int* pathCount, node = prev; do { - path[n++] = node->id; - if (n >= maxPath) + dtNode* next = m_nodePool->getNodeAtIdx(node->pidx); + dtStatus status = 0; + if (node->flags & DT_NODE_PARENT_DETACHED) + { + float t, normal[3]; + int m; + status = raycast(node->id, node->pos, next->pos, m_query.filter, &t, normal, path+n, &m, maxPath-n); + n += m; + // raycast ends on poly boundary and the path might include the next poly boundary. + if (path[n-1] == next->id) + n--; // remove to avoid duplicates + } + else { - m_query.status |= DT_BUFFER_TOO_SMALL; + path[n++] = node->id; + if (n >= maxPath) + status = DT_BUFFER_TOO_SMALL; + } + + if (status & DT_STATUS_DETAIL_MASK) + { + m_query.status |= status & DT_STATUS_DETAIL_MASK; break; } - node = m_nodePool->getNodeAtIdx(node->pidx); + node = next; } while (node); } @@ -1496,7 +1548,7 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPathPartial(const dtPolyRef* existing dtNode* node = 0; for (int i = existingSize-1; i >= 0; --i) { - node = m_nodePool->findNode(existing[i]); + m_nodePool->findNodes(existing[i], &node, 1); if (node) break; } @@ -1509,11 +1561,15 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPathPartial(const dtPolyRef* existing } // Reverse the path. + int prevRay = 0; do { dtNode* next = m_nodePool->getNodeAtIdx(node->pidx); node->pidx = m_nodePool->getNodeIdx(prev); prev = node; + int nextRay = node->flags & DT_NODE_PARENT_DETACHED; // keep track of whether parent is not adjacent (i.e. due to raycast shortcut) + node->flags = (node->flags & ~DT_NODE_PARENT_DETACHED) | prevRay; // and store it in the reversed path's node + prevRay = nextRay; node = next; } while (node); @@ -1522,13 +1578,31 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPathPartial(const dtPolyRef* existing node = prev; do { - path[n++] = node->id; - if (n >= maxPath) + dtNode* next = m_nodePool->getNodeAtIdx(node->pidx); + dtStatus status = 0; + if (node->flags & DT_NODE_PARENT_DETACHED) + { + float t, normal[3]; + int m; + status = raycast(node->id, node->pos, next->pos, m_query.filter, &t, normal, path+n, &m, maxPath-n); + n += m; + // raycast ends on poly boundary and the path might include the next poly boundary. + if (path[n-1] == next->id) + n--; // remove to avoid duplicates + } + else { - m_query.status |= DT_BUFFER_TOO_SMALL; + path[n++] = node->id; + if (n >= maxPath) + status = DT_BUFFER_TOO_SMALL; + } + + if (status & DT_STATUS_DETAIL_MASK) + { + m_query.status |= status & DT_STATUS_DETAIL_MASK; break; } - node = m_nodePool->getNodeAtIdx(node->pidx); + node = next; } while (node); } @@ -1543,6 +1617,87 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPathPartial(const dtPolyRef* existing return DT_SUCCESS | details; } + +dtStatus dtNavMeshQuery::appendVertex(const float* pos, const unsigned char flags, const dtPolyRef ref, + float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, + int* straightPathCount, const int maxStraightPath) const +{ + if ((*straightPathCount) > 0 && dtVequal(&straightPath[((*straightPathCount)-1)*3], pos)) + { + // The vertices are equal, update flags and poly. + if (straightPathFlags) + straightPathFlags[(*straightPathCount)-1] = flags; + if (straightPathRefs) + straightPathRefs[(*straightPathCount)-1] = ref; + } + else + { + // Append new vertex. + dtVcopy(&straightPath[(*straightPathCount)*3], pos); + if (straightPathFlags) + straightPathFlags[(*straightPathCount)] = flags; + if (straightPathRefs) + straightPathRefs[(*straightPathCount)] = ref; + (*straightPathCount)++; + // If reached end of path or there is no space to append more vertices, return. + if (flags == DT_STRAIGHTPATH_END || (*straightPathCount) >= maxStraightPath) + { + return DT_SUCCESS | (((*straightPathCount) >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); + } + } + return DT_IN_PROGRESS; +} + +dtStatus dtNavMeshQuery::appendPortals(const int startIdx, const int endIdx, const float* endPos, const dtPolyRef* path, + float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, + int* straightPathCount, const int maxStraightPath, const int options) const +{ + const float* startPos = &straightPath[(*straightPathCount-1)*3]; + // Append or update last vertex + dtStatus stat = 0; + for (int i = startIdx; i < endIdx; i++) + { + // Calculate portal + const dtPolyRef from = path[i]; + const dtMeshTile* fromTile = 0; + const dtPoly* fromPoly = 0; + if (dtStatusFailed(m_nav->getTileAndPolyByRef(from, &fromTile, &fromPoly))) + return DT_FAILURE | DT_INVALID_PARAM; + + const dtPolyRef to = path[i+1]; + const dtMeshTile* toTile = 0; + const dtPoly* toPoly = 0; + if (dtStatusFailed(m_nav->getTileAndPolyByRef(to, &toTile, &toPoly))) + return DT_FAILURE | DT_INVALID_PARAM; + + float left[3], right[3]; + if (dtStatusFailed(getPortalPoints(from, fromPoly, fromTile, to, toPoly, toTile, left, right))) + break; + + if (options & DT_STRAIGHTPATH_AREA_CROSSINGS) + { + // Skip intersection if only area crossings are requested. + if (fromPoly->getArea() == toPoly->getArea()) + continue; + } + + // Append intersection + float s,t; + if (dtIntersectSegSeg2D(startPos, endPos, left, right, s, t)) + { + float pt[3]; + dtVlerp(pt, left,right, t); + + stat = appendVertex(pt, 0, path[i+1], + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath); + if (stat != DT_IN_PROGRESS) + return stat; + } + } + return DT_IN_PROGRESS; +} + /// @par /// /// This method peforms what is often called 'string pulling'. @@ -1563,7 +1718,7 @@ dtStatus dtNavMeshQuery::finalizeSlicedFindPathPartial(const dtPolyRef* existing dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* endPos, const dtPolyRef* path, const int pathSize, float* straightPath, unsigned char* straightPathFlags, dtPolyRef* straightPathRefs, - int* straightPathCount, const int maxStraightPath) const + int* straightPathCount, const int maxStraightPath, const int options) const { dtAssert(m_nav); @@ -1575,30 +1730,24 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en if (!path[0]) return DT_FAILURE | DT_INVALID_PARAM; - int n = 0; + dtStatus stat = 0; // TODO: Should this be callers responsibility? float closestStartPos[3]; if (dtStatusFailed(closestPointOnPolyBoundary(path[0], startPos, closestStartPos))) return DT_FAILURE | DT_INVALID_PARAM; - - // Add start point. - dtVcopy(&straightPath[n*3], closestStartPos); - if (straightPathFlags) - straightPathFlags[n] = DT_STRAIGHTPATH_START; - if (straightPathRefs) - straightPathRefs[n] = path[0]; - n++; - if (n >= maxStraightPath) - { - *straightPathCount = n; - return DT_SUCCESS | DT_BUFFER_TOO_SMALL; - } - + float closestEndPos[3]; if (dtStatusFailed(closestPointOnPolyBoundary(path[pathSize-1], endPos, closestEndPos))) return DT_FAILURE | DT_INVALID_PARAM; + // Add start point. + stat = appendVertex(closestStartPos, DT_STRAIGHTPATH_START, path[0], + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath); + if (stat != DT_IN_PROGRESS) + return stat; + if (pathSize > 1) { float portalApex[3], portalLeft[3], portalRight[3]; @@ -1633,17 +1782,20 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en // This should only happen when the first polygon is invalid. return DT_FAILURE | DT_INVALID_PARAM; } + + // Apeend portals along the current straight path segment. + if (options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) + { + stat = appendPortals(apexIndex, i, closestEndPos, path, + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath, options); + } + + stat = appendVertex(closestEndPos, 0, path[i], + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath); - dtVcopy(&straightPath[n*3], closestEndPos); - if (straightPathFlags) - straightPathFlags[n] = 0; - if (straightPathRefs) - straightPathRefs[n] = path[i]; - n++; - - *straightPathCount = n; - - return DT_SUCCESS | DT_PARTIAL_RESULT | ((n >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); + return DT_SUCCESS | DT_PARTIAL_RESULT | ((*straightPathCount >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); } // If starting really close the portal, advance. @@ -1675,6 +1827,16 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en } else { + // Append portals along the current straight path segment. + if (options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) + { + stat = appendPortals(apexIndex, leftIndex, portalLeft, path, + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath, options); + if (stat != DT_IN_PROGRESS) + return stat; + } + dtVcopy(portalApex, portalLeft); apexIndex = leftIndex; @@ -1685,30 +1847,12 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en flags = DT_STRAIGHTPATH_OFFMESH_CONNECTION; dtPolyRef ref = leftPolyRef; - if (!dtVequal(&straightPath[(n-1)*3], portalApex)) - { - // Append new vertex. - dtVcopy(&straightPath[n*3], portalApex); - if (straightPathFlags) - straightPathFlags[n] = flags; - if (straightPathRefs) - straightPathRefs[n] = ref; - n++; - // If reached end of path or there is no space to append more vertices, return. - if (flags == DT_STRAIGHTPATH_END || n >= maxStraightPath) - { - *straightPathCount = n; - return DT_SUCCESS | ((n >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); - } - } - else - { - // The vertices are equal, update flags and poly. - if (straightPathFlags) - straightPathFlags[n-1] = flags; - if (straightPathRefs) - straightPathRefs[n-1] = ref; - } + // Append or update vertex + stat = appendVertex(portalApex, flags, ref, + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath); + if (stat != DT_IN_PROGRESS) + return stat; dtVcopy(portalLeft, portalApex); dtVcopy(portalRight, portalApex); @@ -1734,6 +1878,16 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en } else { + // Append portals along the current straight path segment. + if (options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) + { + stat = appendPortals(apexIndex, rightIndex, portalRight, path, + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath, options); + if (stat != DT_IN_PROGRESS) + return stat; + } + dtVcopy(portalApex, portalRight); apexIndex = rightIndex; @@ -1743,31 +1897,13 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en else if (rightPolyType == DT_POLYTYPE_OFFMESH_CONNECTION) flags = DT_STRAIGHTPATH_OFFMESH_CONNECTION; dtPolyRef ref = rightPolyRef; - - if (!dtVequal(&straightPath[(n-1)*3], portalApex)) - { - // Append new vertex. - dtVcopy(&straightPath[n*3], portalApex); - if (straightPathFlags) - straightPathFlags[n] = flags; - if (straightPathRefs) - straightPathRefs[n] = ref; - n++; - // If reached end of path or there is no space to append more vertices, return. - if (flags == DT_STRAIGHTPATH_END || n >= maxStraightPath) - { - *straightPathCount = n; - return DT_SUCCESS | ((n >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); - } - } - else - { - // The vertices are equal, update flags and poly. - if (straightPathFlags) - straightPathFlags[n-1] = flags; - if (straightPathRefs) - straightPathRefs[n-1] = ref; - } + + // Append or update vertex + stat = appendVertex(portalApex, flags, ref, + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath); + if (stat != DT_IN_PROGRESS) + return stat; dtVcopy(portalLeft, portalApex); dtVcopy(portalRight, portalApex); @@ -1781,26 +1917,23 @@ dtStatus dtNavMeshQuery::findStraightPath(const float* startPos, const float* en } } } + + // Append portals along the current straight path segment. + if (options & (DT_STRAIGHTPATH_AREA_CROSSINGS | DT_STRAIGHTPATH_ALL_CROSSINGS)) + { + stat = appendPortals(apexIndex, pathSize-1, closestEndPos, path, + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath, options); + if (stat != DT_IN_PROGRESS) + return stat; + } } + + stat = appendVertex(closestEndPos, DT_STRAIGHTPATH_END, 0, + straightPath, straightPathFlags, straightPathRefs, + straightPathCount, maxStraightPath); - // If the point already exists, remove it and add reappend the actual end location. - if (n > 0 && dtVequal(&straightPath[(n-1)*3], closestEndPos)) - n--; - - // Add end point. - if (n < maxStraightPath) - { - dtVcopy(&straightPath[n*3], closestEndPos); - if (straightPathFlags) - straightPathFlags[n] = DT_STRAIGHTPATH_END; - if (straightPathRefs) - straightPathRefs[n] = 0; - n++; - } - - *straightPathCount = n; - - return DT_SUCCESS | ((n >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); + return DT_SUCCESS | ((*straightPathCount >= maxStraightPath) ? DT_BUFFER_TOO_SMALL : 0); } /// @par @@ -2141,6 +2274,8 @@ dtStatus dtNavMeshQuery::getEdgeMidPoint(dtPolyRef from, const dtPoly* fromPoly, return DT_SUCCESS; } + + /// @par /// /// This method is meant to be used for quick, short distance checks. @@ -2183,73 +2318,147 @@ dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, cons const dtQueryFilter* filter, float* t, float* hitNormal, dtPolyRef* path, int* pathCount, const int maxPath) const { - dtAssert(m_nav); + dtRaycastHit hit; + hit.path = path; + hit.maxPath = maxPath; + + dtStatus status = raycast(startRef, startPos, endPos, filter, 0, &hit); - *t = 0; + *t = hit.t; + if (hitNormal) + dtVcopy(hitNormal, hit.hitNormal); if (pathCount) - *pathCount = 0; + *pathCount = hit.pathCount; + + return status; +} + + +/// @par +/// +/// This method is meant to be used for quick, short distance checks. +/// +/// If the path array is too small to hold the result, it will be filled as +/// far as possible from the start postion toward the end position. +/// +/// Using the Hit Parameter t of RaycastHit +/// +/// If the hit parameter is a very high value (FLT_MAX), then the ray has hit +/// the end position. In this case the path represents a valid corridor to the +/// end position and the value of @p hitNormal is undefined. +/// +/// If the hit parameter is zero, then the start position is on the wall that +/// was hit and the value of @p hitNormal is undefined. +/// +/// If 0 < t < 1.0 then the following applies: +/// +/// @code +/// distanceToHitBorder = distanceToEndPosition * t +/// hitPoint = startPos + (endPos - startPos) * t +/// @endcode +/// +/// Use Case Restriction +/// +/// The raycast ignores the y-value of the end position. (2D check.) This +/// places significant limits on how it can be used. For example: +/// +/// Consider a scene where there is a main floor with a second floor balcony +/// that hangs over the main floor. So the first floor mesh extends below the +/// balcony mesh. The start position is somewhere on the first floor. The end +/// position is on the balcony. +/// +/// The raycast will search toward the end position along the first floor mesh. +/// If it reaches the end position's xz-coordinates it will indicate FLT_MAX +/// (no wall hit), meaning it reached the end position. This is one example of why +/// this method is meant for short distance checks. +/// +dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, const float* endPos, + const dtQueryFilter* filter, const unsigned int options, + dtRaycastHit* hit, dtPolyRef prevRef) const +{ + dtAssert(m_nav); + hit->t = 0; + hit->pathCount = 0; + hit->pathCost = 0; + // Validate input if (!startRef || !m_nav->isValidPolyRef(startRef)) return DT_FAILURE | DT_INVALID_PARAM; + if (prevRef && !m_nav->isValidPolyRef(prevRef)) + return DT_FAILURE | DT_INVALID_PARAM; - dtPolyRef curRef = startRef; - float verts[DT_VERTS_PER_POLYGON*3]; + float dir[3], curPos[3], lastPos[3]; + float verts[DT_VERTS_PER_POLYGON*3+3]; int n = 0; - - hitNormal[0] = 0; - hitNormal[1] = 0; - hitNormal[2] = 0; - + + dtVcopy(curPos, startPos); + dtVsub(dir, endPos, startPos); + dtVset(hit->hitNormal, 0, 0, 0); + dtStatus status = DT_SUCCESS; - + + const dtMeshTile* prevTile, *tile, *nextTile; + const dtPoly* prevPoly, *poly, *nextPoly; + dtPolyRef curRef, nextRef; + + // The API input has been checked already, skip checking internal data. + nextRef = curRef = startRef; + tile = 0; + poly = 0; + m_nav->getTileAndPolyByRefUnsafe(curRef, &tile, &poly); + nextTile = prevTile = tile; + nextPoly = prevPoly = poly; + if (prevRef) + m_nav->getTileAndPolyByRefUnsafe(prevRef, &prevTile, &prevPoly); + while (curRef) { // Cast ray against current polygon. - // The API input has been cheked already, skip checking internal data. - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - m_nav->getTileAndPolyByRefUnsafe(curRef, &tile, &poly); - // Collect vertices. int nv = 0; for (int i = 0; i < (int)poly->vertCount; ++i) { dtVcopy(&verts[nv*3], &tile->verts[poly->verts[i]*3]); nv++; - } + } float tmin, tmax; int segMin, segMax; if (!dtIntersectSegmentPoly2D(startPos, endPos, verts, nv, tmin, tmax, segMin, segMax)) { // Could not hit the polygon, keep the old t and report hit. - if (pathCount) - *pathCount = n; + hit->pathCount = n; return status; } + + hit->hitEdgeIndex = segMax; + // Keep track of furthest t so far. - if (tmax > *t) - *t = tmax; + if (tmax > hit->t) + hit->t = tmax; // Store visited polygons. - if (n < maxPath) - path[n++] = curRef; + if (n < hit->maxPath) + hit->path[n++] = curRef; else status |= DT_BUFFER_TOO_SMALL; - + // Ray end is completely inside the polygon. if (segMax == -1) { - *t = FLT_MAX; - if (pathCount) - *pathCount = n; + hit->t = FLT_MAX; + hit->pathCount = n; + + // add the cost + if (options & DT_RAYCAST_USE_COSTS) + hit->pathCost += filter->getCost(curPos, endPos, prevRef, prevTile, prevPoly, curRef, tile, poly, curRef, tile, poly); return status; } - + // Follow neighbours. - dtPolyRef nextRef = 0; + nextRef = 0; for (unsigned int i = poly->firstLink; i != DT_NULL_LINK; i = tile->links[i].next) { @@ -2260,8 +2469,8 @@ dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, cons continue; // Get pointer to the next polygon. - const dtMeshTile* nextTile = 0; - const dtPoly* nextPoly = 0; + nextTile = 0; + nextPoly = 0; m_nav->getTileAndPolyByRefUnsafe(link->ref, &nextTile, &nextPoly); // Skip off-mesh connections. @@ -2329,6 +2538,24 @@ dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, cons } } + // add the cost + if (options & DT_RAYCAST_USE_COSTS) + { + // compute the intersection point at the furthest end of the polygon + // and correct the height (since the raycast moves in 2d) + dtVcopy(lastPos, curPos); + dtVmad(curPos, startPos, dir, hit->t); + float* e1 = &verts[segMax*3]; + float* e2 = &verts[((segMax+1)%nv)*3]; + float eDir[3], diff[3]; + dtVsub(eDir, e2, e1); + dtVsub(diff, curPos, e1); + float s = dtSqr(eDir[0]) > dtSqr(eDir[2]) ? diff[0] / eDir[0] : diff[2] / eDir[2]; + curPos[1] = e1[1] + eDir[1] * s; + + hit->pathCost += filter->getCost(lastPos, curPos, prevRef, prevTile, prevPoly, curRef, tile, poly, nextRef, nextTile, nextPoly); + } + if (!nextRef) { // No neighbour, we hit a wall. @@ -2340,22 +2567,25 @@ dtStatus dtNavMeshQuery::raycast(dtPolyRef startRef, const float* startPos, cons const float* vb = &verts[b*3]; const float dx = vb[0] - va[0]; const float dz = vb[2] - va[2]; - hitNormal[0] = dz; - hitNormal[1] = 0; - hitNormal[2] = -dx; - dtVnormalize(hitNormal); + hit->hitNormal[0] = dz; + hit->hitNormal[1] = 0; + hit->hitNormal[2] = -dx; + dtVnormalize(hit->hitNormal); - if (pathCount) - *pathCount = n; + hit->pathCount = n; return status; } - + // No hit, advance to neighbour polygon. + prevRef = curRef; curRef = nextRef; + prevTile = tile; + tile = nextTile; + prevPoly = poly; + poly = nextPoly; } - if (pathCount) - *pathCount = n; + hit->pathCount = n; return status; } @@ -3286,7 +3516,7 @@ dtStatus dtNavMeshQuery::findDistanceToWall(dtPolyRef startRef, const float* cen dtVsub(hitNormal, centerPos, hitPos); dtVnormalize(hitNormal); - *hitDist = sqrtf(radiusSqr); + *hitDist = dtMathSqrtf(radiusSqr); return status; } @@ -3313,6 +3543,15 @@ bool dtNavMeshQuery::isValidPolyRef(dtPolyRef ref, const dtQueryFilter* filter) bool dtNavMeshQuery::isInClosedList(dtPolyRef ref) const { if (!m_nodePool) return false; - const dtNode* node = m_nodePool->findNode(ref); - return node && node->flags & DT_NODE_CLOSED; + + dtNode* nodes[DT_MAX_STATES_PER_NODE]; + int n= m_nodePool->findNodes(ref, nodes, DT_MAX_STATES_PER_NODE); + + for (int i=0; iflags & DT_NODE_CLOSED) + return true; + } + + return false; } diff --git a/Engine/lib/recast/Detour/Source/DetourNode.cpp b/Engine/lib/recast/Detour/Source/DetourNode.cpp index de7b159bfa..48abbba6b5 100644 --- a/Engine/lib/recast/Detour/Source/DetourNode.cpp +++ b/Engine/lib/recast/Detour/Source/DetourNode.cpp @@ -22,6 +22,19 @@ #include "DetourCommon.h" #include +#ifdef DT_POLYREF64 +// From Thomas Wang, https://gist.github.com/badboy/6267743 +inline unsigned int dtHashRef(dtPolyRef a) +{ + a = (~a) + (a << 18); // a = (a << 18) - a - 1; + a = a ^ (a >> 31); + a = a * 21; // a = (a + (a << 2)) + (a << 4); + a = a ^ (a >> 11); + a = a + (a << 6); + a = a ^ (a >> 22); + return (unsigned int)a; +} +#else inline unsigned int dtHashRef(dtPolyRef a) { a += ~(a<<15); @@ -32,6 +45,7 @@ inline unsigned int dtHashRef(dtPolyRef a) a ^= (a>>16); return (unsigned int)a; } +#endif ////////////////////////////////////////////////////////////////////////////////////////// dtNodePool::dtNodePool(int maxNodes, int hashSize) : @@ -43,7 +57,9 @@ dtNodePool::dtNodePool(int maxNodes, int hashSize) : m_nodeCount(0) { dtAssert(dtNextPow2(m_hashSize) == (unsigned int)m_hashSize); - dtAssert(m_maxNodes > 0); + // pidx is special as 0 means "none" and 1 is the first node. For that reason + // we have 1 fewer nodes available than the number of values it can contain. + dtAssert(m_maxNodes > 0 && m_maxNodes <= DT_NULL_IDX && m_maxNodes <= (1 << DT_NODE_PARENT_BITS) - 1); m_nodes = (dtNode*)dtAlloc(sizeof(dtNode)*m_maxNodes, DT_ALLOC_PERM); m_next = (dtNodeIndex*)dtAlloc(sizeof(dtNodeIndex)*m_maxNodes, DT_ALLOC_PERM); @@ -70,27 +86,46 @@ void dtNodePool::clear() m_nodeCount = 0; } -dtNode* dtNodePool::findNode(dtPolyRef id) +unsigned int dtNodePool::findNodes(dtPolyRef id, dtNode** nodes, const int maxNodes) { + int n = 0; unsigned int bucket = dtHashRef(id) & (m_hashSize-1); dtNodeIndex i = m_first[bucket]; while (i != DT_NULL_IDX) { if (m_nodes[i].id == id) + { + if (n >= maxNodes) + return n; + nodes[n++] = &m_nodes[i]; + } + i = m_next[i]; + } + + return n; +} + +dtNode* dtNodePool::findNode(dtPolyRef id, unsigned char state) +{ + unsigned int bucket = dtHashRef(id) & (m_hashSize-1); + dtNodeIndex i = m_first[bucket]; + while (i != DT_NULL_IDX) + { + if (m_nodes[i].id == id && m_nodes[i].state == state) return &m_nodes[i]; i = m_next[i]; } return 0; } -dtNode* dtNodePool::getNode(dtPolyRef id) +dtNode* dtNodePool::getNode(dtPolyRef id, unsigned char state) { unsigned int bucket = dtHashRef(id) & (m_hashSize-1); dtNodeIndex i = m_first[bucket]; dtNode* node = 0; while (i != DT_NULL_IDX) { - if (m_nodes[i].id == id) + if (m_nodes[i].id == id && m_nodes[i].state == state) return &m_nodes[i]; i = m_next[i]; } @@ -107,6 +142,7 @@ dtNode* dtNodePool::getNode(dtPolyRef id) node->cost = 0; node->total = 0; node->id = id; + node->state = state; node->flags = 0; m_next[i] = m_first[bucket]; diff --git a/Engine/lib/recast/DetourCrowd/CMakeLists.txt b/Engine/lib/recast/DetourCrowd/CMakeLists.txt deleted file mode 100644 index 0c34e1bd34..0000000000 --- a/Engine/lib/recast/DetourCrowd/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) - -SET(detourcrowd_SRCS - Source/DetourPathCorridor.cpp - Source/DetourLocalBoundary.cpp - Source/DetourObstacleAvoidance.cpp - Source/DetourPathQueue.cpp - Source/DetourCrowd.cpp - Source/DetourProximityGrid.cpp -) - -SET(detourcrowd_HDRS - Include/DetourPathCorridor.h - Include/DetourCrowd.h - Include/DetourObstacleAvoidance.h - Include/DetourLocalBoundary.h - Include/DetourProximityGrid.h - Include/DetourPathQueue.h -) - -INCLUDE_DIRECTORIES(Include - ../Detour/Include - ../DetourTileCache - ../Recast/Include -) - -ADD_LIBRARY(DetourCrowd ${detourcrowd_SRCS} ${detourcrowd_HDRS}) diff --git a/Engine/lib/recast/DetourCrowd/Include/DetourCrowd.h b/Engine/lib/recast/DetourCrowd/Include/DetourCrowd.h index e789fd34ee..2a2003b169 100644 --- a/Engine/lib/recast/DetourCrowd/Include/DetourCrowd.h +++ b/Engine/lib/recast/DetourCrowd/Include/DetourCrowd.h @@ -45,6 +45,12 @@ static const int DT_CROWDAGENT_MAX_CORNERS = 4; /// 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 @@ -87,6 +93,9 @@ struct dtCrowdAgentParams /// [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; }; @@ -106,12 +115,15 @@ enum MoveRequestState /// @ingroup crowd struct dtCrowdAgent { - /// 1 if the agent is active, or 0 if the agent is in an unused slot in the agent pool. - unsigned char active; + /// 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; @@ -131,10 +143,10 @@ struct dtCrowdAgent float desiredSpeed; float npos[3]; ///< The current agent position. [(x, y, z)] - float disp[3]; - float dvel[3]; ///< The desired velocity of the agent. [(x, y, z)] - float nvel[3]; - float vel[3]; ///< The actual velocity of the agent. [(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; @@ -161,7 +173,7 @@ struct dtCrowdAgent struct dtCrowdAgentAnimation { - unsigned char active; + bool active; float initPos[3], startPos[3], endPos[3]; dtPolyRef polyRef; float t, tmax; @@ -206,8 +218,9 @@ class dtCrowd int m_maxPathResult; float m_ext[3]; - dtQueryFilter m_filter; - + + dtQueryFilter m_filters[DT_CROWD_MAX_QUERY_FILTER_TYPE]; + float m_maxAgentRadius; int m_velocitySampleCount; @@ -218,7 +231,7 @@ class dtCrowd void updateMoveRequest(const float dt); void checkPathValidity(dtCrowdAgent** agents, const int nagents, const float dt); - inline int getAgentIndex(const dtCrowdAgent* agent) const { return agent - m_agents; } + inline int getAgentIndex(const dtCrowdAgent* agent) const { return (int)(agent - m_agents); } bool requestMoveTargetReplan(const int idx, dtPolyRef ref, const float* pos); @@ -251,9 +264,14 @@ class dtCrowd /// @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. - const int getAgentCount() const; + int getAgentCount() const; /// Adds a new agent to the crowd. /// @param[in] pos The requested position of the agent. [(x, y, z)] @@ -301,11 +319,11 @@ class dtCrowd /// Gets the filter used by the crowd. /// @return The filter used by the crowd. - const dtQueryFilter* getFilter() const { return &m_filter; } - + 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. - dtQueryFilter* getEditableFilter() { return &m_filter; } + inline dtQueryFilter* getEditableFilter(const int i) { return (i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE) ? &m_filters[i] : 0; } /// Gets the search extents [(x, y, z)] used by the crowd for query operations. /// @return The search extents used by the crowd. [(x, y, z)] @@ -325,6 +343,11 @@ class dtCrowd /// 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. @@ -429,4 +452,4 @@ This value is often based on the agent radius. E.g. radius * 30 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. -*/ \ No newline at end of file +*/ diff --git a/Engine/lib/recast/DetourCrowd/Include/DetourLocalBoundary.h b/Engine/lib/recast/DetourCrowd/Include/DetourLocalBoundary.h index d77a13690b..56b5cb27c5 100644 --- a/Engine/lib/recast/DetourCrowd/Include/DetourLocalBoundary.h +++ b/Engine/lib/recast/DetourCrowd/Include/DetourLocalBoundary.h @@ -40,7 +40,7 @@ class dtLocalBoundary dtPolyRef m_polys[MAX_LOCAL_POLYS]; int m_npolys; - void addSegment(const float dist, const float* seg); + void addSegment(const float dist, const float* s); public: dtLocalBoundary(); @@ -56,6 +56,11 @@ class dtLocalBoundary 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 diff --git a/Engine/lib/recast/DetourCrowd/Include/DetourObstacleAvoidance.h b/Engine/lib/recast/DetourCrowd/Include/DetourObstacleAvoidance.h index 8ff6211e86..cc46347a0f 100644 --- a/Engine/lib/recast/DetourCrowd/Include/DetourObstacleAvoidance.h +++ b/Engine/lib/recast/DetourCrowd/Include/DetourObstacleAvoidance.h @@ -58,6 +58,10 @@ class dtObstacleAvoidanceDebugData 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; @@ -122,17 +126,18 @@ class dtObstacleAvoidanceQuery 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); - dtObstacleCircle* insertCircle(const float dist); - dtObstacleSegment* insertSegment(const float dist); - dtObstacleAvoidanceParams m_params; float m_invHorizTime; float m_vmax; diff --git a/Engine/lib/recast/DetourCrowd/Include/DetourPathCorridor.h b/Engine/lib/recast/DetourCrowd/Include/DetourPathCorridor.h index 9544ea52d2..f5ee8df70d 100644 --- a/Engine/lib/recast/DetourCrowd/Include/DetourPathCorridor.h +++ b/Engine/lib/recast/DetourCrowd/Include/DetourPathCorridor.h @@ -92,14 +92,16 @@ class dtPathCorridor /// @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. - void movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter); + /// @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. - void moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter); + /// @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)] @@ -129,7 +131,12 @@ class dtPathCorridor /// 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; } + 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, diff --git a/Engine/lib/recast/DetourCrowd/Include/DetourPathQueue.h b/Engine/lib/recast/DetourCrowd/Include/DetourPathQueue.h index fe3920b600..310721e709 100644 --- a/Engine/lib/recast/DetourCrowd/Include/DetourPathQueue.h +++ b/Engine/lib/recast/DetourCrowd/Include/DetourPathQueue.h @@ -70,6 +70,10 @@ class dtPathQueue 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 diff --git a/Engine/lib/recast/DetourCrowd/Include/DetourProximityGrid.h b/Engine/lib/recast/DetourCrowd/Include/DetourProximityGrid.h index b098261e28..d8968efd56 100644 --- a/Engine/lib/recast/DetourCrowd/Include/DetourProximityGrid.h +++ b/Engine/lib/recast/DetourCrowd/Include/DetourProximityGrid.h @@ -21,7 +21,6 @@ class dtProximityGrid { - int m_maxItems; float m_cellSize; float m_invCellSize; @@ -44,7 +43,7 @@ class dtProximityGrid dtProximityGrid(); ~dtProximityGrid(); - bool init(const int maxItems, const float cellSize); + bool init(const int poolSize, const float cellSize); void clear(); @@ -59,7 +58,12 @@ class dtProximityGrid int getItemCountAt(const int x, const int y) const; inline const int* getBounds() const { return m_bounds; } - inline const float getCellSize() const { return m_cellSize; } + 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(); diff --git a/Engine/lib/recast/DetourCrowd/Source/DetourCrowd.cpp b/Engine/lib/recast/DetourCrowd/Source/DetourCrowd.cpp index e7312efda7..f9f4360778 100644 --- a/Engine/lib/recast/DetourCrowd/Source/DetourCrowd.cpp +++ b/Engine/lib/recast/DetourCrowd/Source/DetourCrowd.cpp @@ -17,7 +17,6 @@ // #define _USE_MATH_DEFINES -#include #include #include #include @@ -27,6 +26,7 @@ #include "DetourNavMeshQuery.h" #include "DetourObstacleAvoidance.h" #include "DetourCommon.h" +#include "DetourMath.h" #include "DetourAssert.h" #include "DetourAlloc.h" @@ -206,7 +206,7 @@ static int getNeighbours(const float* pos, const float height, const float range // Check for overlap. float diff[3]; dtVsub(diff, pos, ag->npos); - if (fabsf(diff[1]) >= (height+ag->params.height)/2.0f) + if (dtMathFabsf(diff[1]) >= (height+ag->params.height)/2.0f) continue; diff[1] = 0; const float distSqr = dtVlenSqr(diff); @@ -441,14 +441,14 @@ bool dtCrowd::init(const int maxAgents, const float maxAgentRadius, dtNavMesh* n for (int i = 0; i < m_maxAgents; ++i) { new(&m_agents[i]) dtCrowdAgent(); - m_agents[i].active = 0; + m_agents[i].active = false; if (!m_agents[i].corridor.init(m_maxPathResult)) return false; } for (int i = 0; i < m_maxAgents; ++i) { - m_agentAnims[i].active = 0; + m_agentAnims[i].active = false; } // The navquery is mostly used for local searches, no need for large node pool. @@ -474,7 +474,7 @@ const dtObstacleAvoidanceParams* dtCrowd::getObstacleAvoidanceParams(const int i return 0; } -const int dtCrowd::getAgentCount() const +int dtCrowd::getAgentCount() const { return m_maxAgents; } @@ -484,12 +484,23 @@ const int dtCrowd::getAgentCount() const /// Agents in the pool may not be in use. Check #dtCrowdAgent.active before using the returned object. const dtCrowdAgent* dtCrowd::getAgent(const int idx) { + if (idx < 0 || idx >= m_maxAgents) + return 0; + return &m_agents[idx]; +} + +/// +/// Agents in the pool may not be in use. Check #dtCrowdAgent.active before using the returned object. +dtCrowdAgent* dtCrowd::getEditableAgent(const int idx) +{ + if (idx < 0 || idx >= m_maxAgents) + return 0; return &m_agents[idx]; } void dtCrowd::updateAgentParameters(const int idx, const dtCrowdAgentParams* params) { - if (idx < 0 || idx > m_maxAgents) + if (idx < 0 || idx >= m_maxAgents) return; memcpy(&m_agents[idx].params, params, sizeof(dtCrowdAgentParams)); } @@ -512,18 +523,25 @@ int dtCrowd::addAgent(const float* pos, const dtCrowdAgentParams* params) if (idx == -1) return -1; - dtCrowdAgent* ag = &m_agents[idx]; + dtCrowdAgent* ag = &m_agents[idx]; + updateAgentParameters(idx, params); + // Find nearest position on navmesh and place the agent there. float nearest[3]; - dtPolyRef ref; - m_navquery->findNearestPoly(pos, m_ext, &m_filter, &ref, nearest); + dtPolyRef ref = 0; + dtVcopy(nearest, pos); + dtStatus status = m_navquery->findNearestPoly(pos, m_ext, &m_filters[ag->params.queryFilterType], &ref, nearest); + if (dtStatusFailed(status)) + { + dtVcopy(nearest, pos); + ref = 0; + } ag->corridor.reset(ref, nearest); ag->boundary.reset(); + ag->partial = false; - updateAgentParameters(idx, params); - ag->topologyOptTime = 0; ag->targetReplanTime = 0; ag->nneis = 0; @@ -542,7 +560,7 @@ int dtCrowd::addAgent(const float* pos, const dtCrowdAgentParams* params) ag->targetState = DT_CROWDAGENT_TARGET_NONE; - ag->active = 1; + ag->active = true; return idx; } @@ -555,13 +573,13 @@ void dtCrowd::removeAgent(const int idx) { if (idx >= 0 && idx < m_maxAgents) { - m_agents[idx].active = 0; + m_agents[idx].active = false; } } bool dtCrowd::requestMoveTargetReplan(const int idx, dtPolyRef ref, const float* pos) { - if (idx < 0 || idx > m_maxAgents) + if (idx < 0 || idx >= m_maxAgents) return false; dtCrowdAgent* ag = &m_agents[idx]; @@ -588,7 +606,7 @@ bool dtCrowd::requestMoveTargetReplan(const int idx, dtPolyRef ref, const float* /// The request will be processed during the next #update(). bool dtCrowd::requestMoveTarget(const int idx, dtPolyRef ref, const float* pos) { - if (idx < 0 || idx > m_maxAgents) + if (idx < 0 || idx >= m_maxAgents) return false; if (!ref) return false; @@ -610,7 +628,7 @@ bool dtCrowd::requestMoveTarget(const int idx, dtPolyRef ref, const float* pos) bool dtCrowd::requestMoveVelocity(const int idx, const float* vel) { - if (idx < 0 || idx > m_maxAgents) + if (idx < 0 || idx >= m_maxAgents) return false; dtCrowdAgent* ag = &m_agents[idx]; @@ -627,7 +645,7 @@ bool dtCrowd::requestMoveVelocity(const int idx, const float* vel) bool dtCrowd::resetMoveTarget(const int idx) { - if (idx < 0 || idx > m_maxAgents) + if (idx < 0 || idx >= m_maxAgents) return false; dtCrowdAgent* ag = &m_agents[idx]; @@ -635,6 +653,7 @@ bool dtCrowd::resetMoveTarget(const int idx) // Initialize request. ag->targetRef = 0; dtVset(ag->targetPos, 0,0,0); + dtVset(ag->dvel, 0,0,0); ag->targetPathqRef = DT_PATHQ_INVALID; ag->targetReplan = false; ag->targetState = DT_CROWDAGENT_TARGET_NONE; @@ -683,9 +702,9 @@ void dtCrowd::updateMoveRequest(const float /*dt*/) dtPolyRef reqPath[MAX_RES]; // The path to the request location int reqPathCount = 0; - // Quick seach towards the goal. + // Quick search towards the goal. static const int MAX_ITER = 20; - m_navquery->initSlicedFindPath(path[0], ag->targetRef, ag->npos, ag->targetPos, &m_filter); + m_navquery->initSlicedFindPath(path[0], ag->targetRef, ag->npos, ag->targetPos, &m_filters[ag->params.queryFilterType]); m_navquery->updateSlicedFindPath(MAX_ITER, 0); dtStatus status = 0; if (ag->targetReplan) // && npath > 10) @@ -705,7 +724,7 @@ void dtCrowd::updateMoveRequest(const float /*dt*/) if (reqPath[reqPathCount-1] != ag->targetRef) { // Partial path, constrain target position inside the last polygon. - status = m_navquery->closestPointOnPoly(reqPath[reqPathCount-1], ag->targetPos, reqPos); + status = m_navquery->closestPointOnPoly(reqPath[reqPathCount-1], ag->targetPos, reqPos, 0); if (dtStatusFailed(status)) reqPathCount = 0; } @@ -729,6 +748,7 @@ void dtCrowd::updateMoveRequest(const float /*dt*/) ag->corridor.setCorridor(reqPos, reqPath, reqPathCount); ag->boundary.reset(); + ag->partial = false; if (reqPath[reqPathCount-1] == ag->targetRef) { @@ -752,7 +772,7 @@ void dtCrowd::updateMoveRequest(const float /*dt*/) { dtCrowdAgent* ag = queue[i]; ag->targetPathqRef = m_pathq.request(ag->corridor.getLastPoly(), ag->targetRef, - ag->corridor.getTarget(), ag->targetPos, &m_filter); + ag->corridor.getTarget(), ag->targetPos, &m_filters[ag->params.queryFilterType]); if (ag->targetPathqRef != DT_PATHQ_INVALID) ag->targetState = DT_CROWDAGENT_TARGET_WAITING_FOR_PATH; } @@ -802,7 +822,12 @@ void dtCrowd::updateMoveRequest(const float /*dt*/) status = m_pathq.getPathResult(ag->targetPathqRef, res, &nres, m_maxPathResult); if (dtStatusFailed(status) || !nres) valid = false; - + + if (dtStatusDetail(status, DT_PARTIAL_RESULT)) + ag->partial = true; + else + ag->partial = false; + // Merge result and existing path. // The agent might have moved whilst the request is // being processed, so the path may have changed. @@ -849,7 +874,7 @@ void dtCrowd::updateMoveRequest(const float /*dt*/) { // Partial path, constrain target position inside the last polygon. float nearest[3]; - status = m_navquery->closestPointOnPoly(res[nres-1], targetPos, nearest); + status = m_navquery->closestPointOnPoly(res[nres-1], targetPos, nearest, 0); if (dtStatusSucceed(status)) dtVcopy(targetPos, nearest); else @@ -906,7 +931,7 @@ void dtCrowd::updateTopologyOptimization(dtCrowdAgent** agents, const int nagent for (int i = 0; i < nqueue; ++i) { dtCrowdAgent* ag = queue[i]; - ag->corridor.optimizePathTopology(m_navquery, &m_filter); + ag->corridor.optimizePathTopology(m_navquery, &m_filters[ag->params.queryFilterType]); ag->topologyOptTime = 0; } @@ -923,9 +948,6 @@ void dtCrowd::checkPathValidity(dtCrowdAgent** agents, const int nagents, const if (ag->state != DT_CROWDAGENT_STATE_WALKING) continue; - - if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY) - continue; ag->targetReplanTime += dt; @@ -936,19 +958,21 @@ void dtCrowd::checkPathValidity(dtCrowdAgent** agents, const int nagents, const float agentPos[3]; dtPolyRef agentRef = ag->corridor.getFirstPoly(); dtVcopy(agentPos, ag->npos); - if (!m_navquery->isValidPolyRef(agentRef, &m_filter)) + if (!m_navquery->isValidPolyRef(agentRef, &m_filters[ag->params.queryFilterType])) { // Current location is not valid, try to reposition. // TODO: this can snap agents, how to handle that? float nearest[3]; + dtVcopy(nearest, agentPos); agentRef = 0; - m_navquery->findNearestPoly(ag->npos, m_ext, &m_filter, &agentRef, nearest); + m_navquery->findNearestPoly(ag->npos, m_ext, &m_filters[ag->params.queryFilterType], &agentRef, nearest); dtVcopy(agentPos, nearest); if (!agentRef) { // Could not find location in navmesh, set state to invalid. ag->corridor.reset(0, agentPos); + ag->partial = false; ag->boundary.reset(); ag->state = DT_CROWDAGENT_STATE_INVALID; continue; @@ -964,14 +988,20 @@ void dtCrowd::checkPathValidity(dtCrowdAgent** agents, const int nagents, const replan = true; } + // If the agent does not have move target or is controlled by velocity, no need to recover the target nor replan. + if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY) + continue; + // Try to recover move request position. if (ag->targetState != DT_CROWDAGENT_TARGET_NONE && ag->targetState != DT_CROWDAGENT_TARGET_FAILED) { - if (!m_navquery->isValidPolyRef(ag->targetRef, &m_filter)) + if (!m_navquery->isValidPolyRef(ag->targetRef, &m_filters[ag->params.queryFilterType])) { // Current target is not valid, try to reposition. float nearest[3]; - m_navquery->findNearestPoly(ag->targetPos, m_ext, &m_filter, &ag->targetRef, nearest); + dtVcopy(nearest, ag->targetPos); + ag->targetRef = 0; + m_navquery->findNearestPoly(ag->targetPos, m_ext, &m_filters[ag->params.queryFilterType], &ag->targetRef, nearest); dtVcopy(ag->targetPos, nearest); replan = true; } @@ -979,12 +1009,13 @@ void dtCrowd::checkPathValidity(dtCrowdAgent** agents, const int nagents, const { // Failed to reposition target, fail moverequest. ag->corridor.reset(agentRef, agentPos); + ag->partial = false; ag->targetState = DT_CROWDAGENT_TARGET_NONE; } } // If nearby corridor is not valid, replan. - if (!ag->corridor.isValid(CHECK_LOOKAHEAD, m_navquery, &m_filter)) + if (!ag->corridor.isValid(CHECK_LOOKAHEAD, m_navquery, &m_filters[ag->params.queryFilterType])) { // Fix current path. // ag->corridor.trimInvalidPath(agentRef, agentPos, m_navquery, &m_filter); @@ -1020,7 +1051,7 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) dtCrowdAgent** agents = m_activeAgents; int nagents = getActiveAgents(agents, m_maxAgents); - + // Check that all agents still have valid paths. checkPathValidity(agents, nagents, dt); @@ -1051,10 +1082,10 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) // if it has become invalid. const float updateThr = ag->params.collisionQueryRange*0.25f; if (dtVdist2DSqr(ag->npos, ag->boundary.getCenter()) > dtSqr(updateThr) || - !ag->boundary.isValid(m_navquery, &m_filter)) + !ag->boundary.isValid(m_navquery, &m_filters[ag->params.queryFilterType])) { ag->boundary.update(ag->corridor.getFirstPoly(), ag->npos, ag->params.collisionQueryRange, - m_navquery, &m_filter); + m_navquery, &m_filters[ag->params.queryFilterType]); } // Query neighbour agents ag->nneis = getNeighbours(ag->npos, ag->params.height, ag->params.collisionQueryRange, @@ -1076,14 +1107,14 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) // Find corners for steering ag->ncorners = ag->corridor.findCorners(ag->cornerVerts, ag->cornerFlags, ag->cornerPolys, - DT_CROWDAGENT_MAX_CORNERS, m_navquery, &m_filter); + DT_CROWDAGENT_MAX_CORNERS, m_navquery, &m_filters[ag->params.queryFilterType]); // Check to see if the corner after the next corner is directly visible, // and short cut to there. if ((ag->params.updateFlags & DT_CROWD_OPTIMIZE_VIS) && ag->ncorners > 0) { const float* target = &ag->cornerVerts[dtMin(1,ag->ncorners-1)*3]; - ag->corridor.optimizePathVisibility(target, ag->params.pathOptimizationRange, m_navquery, &m_filter); + ag->corridor.optimizePathVisibility(target, ag->params.pathOptimizationRange, m_navquery, &m_filters[ag->params.queryFilterType]); // Copy data for debug purposes. if (debugIdx == i) @@ -1118,7 +1149,7 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) if (overOffmeshConnection(ag, triggerRadius)) { // Prepare to off-mesh connection. - const int idx = ag - m_agents; + const int idx = (int)(ag - m_agents); dtCrowdAgentAnimation* anim = &m_agentAnims[idx]; // Adjust the path over the off-mesh connection. @@ -1128,7 +1159,7 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) { dtVcopy(anim->initPos, ag->npos); anim->polyRef = refs[1]; - anim->active = 1; + anim->active = true; anim->t = 0.0f; anim->tmax = (dtVdist2D(anim->startPos, anim->endPos) / ag->params.maxSpeed) * 0.5f; @@ -1200,7 +1231,7 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) continue; if (distSqr > dtSqr(separationDist)) continue; - const float dist = sqrtf(distSqr); + const float dist = dtMathSqrtf(distSqr); const float weight = separationWeight * (1.0f - dtSqr(dist*invSeparationDist)); dtVmad(disp, disp, diff, weight/dist); @@ -1318,7 +1349,7 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) float dist = dtVlenSqr(diff); if (dist > dtSqr(ag->params.radius + nei->params.radius)) continue; - dist = sqrtf(dist); + dist = dtMathSqrtf(dist); float pen = (ag->params.radius + nei->params.radius) - dist; if (dist < 0.0001f) { @@ -1363,7 +1394,7 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) continue; // Move along navmesh. - ag->corridor.movePosition(ag->npos, m_navquery, &m_filter); + ag->corridor.movePosition(ag->npos, m_navquery, &m_filters[ag->params.queryFilterType]); // Get valid constrained position back. dtVcopy(ag->npos, ag->corridor.getPos()); @@ -1371,6 +1402,7 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) if (ag->targetState == DT_CROWDAGENT_TARGET_NONE || ag->targetState == DT_CROWDAGENT_TARGET_VELOCITY) { ag->corridor.reset(ag->corridor.getFirstPoly(), ag->npos); + ag->partial = false; } } @@ -1387,7 +1419,7 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) if (anim->t > anim->tmax) { // Reset animation - anim->active = 0; + anim->active = false; // Prepare agent for walking. ag->state = DT_CROWDAGENT_STATE_WALKING; continue; @@ -1413,5 +1445,3 @@ void dtCrowd::update(const float dt, dtCrowdAgentDebugInfo* debug) } } - - diff --git a/Engine/lib/recast/DetourCrowd/Source/DetourObstacleAvoidance.cpp b/Engine/lib/recast/DetourCrowd/Source/DetourObstacleAvoidance.cpp index d3f90b7ab1..8466c813a7 100644 --- a/Engine/lib/recast/DetourCrowd/Source/DetourObstacleAvoidance.cpp +++ b/Engine/lib/recast/DetourCrowd/Source/DetourObstacleAvoidance.cpp @@ -18,10 +18,10 @@ #include "DetourObstacleAvoidance.h" #include "DetourCommon.h" +#include "DetourMath.h" #include "DetourAlloc.h" #include "DetourAssert.h" #include -#include #include #include @@ -44,7 +44,7 @@ static int sweepCircleCircle(const float* c0, const float r0, const float* v, float d = b*b - a*c; if (d < 0.0f) return 0; // no intersection. a = 1.0f / a; - const float rd = dtSqrt(d); + const float rd = dtMathSqrtf(d); tmin = (b - rd) * a; tmax = (b + rd) * a; return 1; @@ -58,7 +58,7 @@ static int isectRaySeg(const float* ap, const float* u, dtVsub(v,bq,bp); dtVsub(w,ap,bp); float d = dtVperp2D(u,v); - if (fabsf(d) < 1e-6f) return 0; + if (dtMathFabsf(d) < 1e-6f) return 0; d = 1.0f/d; t = dtVperp2D(v,w) * d; if (t < 0 || t > 1) return 0; @@ -262,7 +262,7 @@ void dtObstacleAvoidanceQuery::addCircle(const float* pos, const float rad, void dtObstacleAvoidanceQuery::addSegment(const float* p, const float* q) { - if (m_nsegments > m_maxSegments) + if (m_nsegments >= m_maxSegments) return; dtObstacleSegment* seg = &m_segments[m_nsegments++]; @@ -281,7 +281,7 @@ void dtObstacleAvoidanceQuery::prepare(const float* pos, const float* dvel) const float* pa = pos; const float* pb = cir->p; - const float orig[3] = {0,0}; + const float orig[3] = {0,0,0}; float dv[3]; dtVsub(cir->dp,pb,pa); dtVnormalize(cir->dp); @@ -311,11 +311,30 @@ void dtObstacleAvoidanceQuery::prepare(const float* pos, const float* dvel) } } + +/* 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; @@ -350,7 +369,11 @@ float dtObstacleAvoidanceQuery::processSample(const float* vcand, const float cs { // The closest obstacle is somewhere ahead of us, keep track of nearest obstacle. if (htmin < tmin) + { tmin = htmin; + if (tmin < tThresold) + return minPenalty; + } } } @@ -383,15 +406,17 @@ float dtObstacleAvoidanceQuery::processSample(const float* vcand, const float cs // 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 vpen = m_params.weightDesVel * (dtVdist2D(vcand, dvel) * m_invVmax); - const float vcpen = m_params.weightCurVel * (dtVdist2D(vcand, vel) * m_invVmax); const float spen = m_params.weightSide * side; const float tpen = m_params.weightToi * (1.0f/(0.1f+tmin*m_invHorizTime)); @@ -414,7 +439,7 @@ int dtObstacleAvoidanceQuery::sampleVelocityGrid(const float* pos, const float r memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams)); m_invHorizTime = 1.0f / m_params.horizTime; m_vmax = vmax; - m_invVmax = 1.0f / vmax; + m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX; dtVset(nvel, 0,0,0); @@ -440,7 +465,7 @@ int dtObstacleAvoidanceQuery::sampleVelocityGrid(const float* pos, const float r if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+cs/2)) continue; - const float penalty = processSample(vcand, cs, pos,rad,vel,dvel, debug); + const float penalty = processSample(vcand, cs, pos,rad,vel,dvel, minPenalty, debug); ns++; if (penalty < minPenalty) { @@ -454,6 +479,28 @@ int dtObstacleAvoidanceQuery::sampleVelocityGrid(const float* pos, const float r } +// 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, @@ -464,7 +511,7 @@ int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const flo memcpy(&m_params, params, sizeof(dtObstacleAvoidanceParams)); m_invHorizTime = 1.0f / m_params.horizTime; m_vmax = vmax; - m_invVmax = 1.0f / vmax; + m_invVmax = vmax > 0 ? 1.0f / vmax : FLT_MAX; dtVset(nvel, 0,0,0); @@ -482,8 +529,15 @@ int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const flo 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 dang = atan2f(dvel[2], dvel[0]); - + 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; @@ -492,16 +546,35 @@ int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const flo for (int j = 0; j < nr; ++j) { const float r = (float)(nr-j)/(float)nr; - float a = dang + (j&1)*0.5f*da; - for (int i = 0; i < nd; ++i) + 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) { - pat[npat*2+0] = cosf(a)*r; - pat[npat*2+1] = sinf(a)*r; + // 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++; - a += da; } } + // Start sampling. float cr = vmax * (1.0f - m_params.velBias); float res[3]; @@ -523,7 +596,7 @@ int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const flo if (dtSqr(vcand[0])+dtSqr(vcand[2]) > dtSqr(vmax+0.001f)) continue; - const float penalty = processSample(vcand,cr/10, pos,rad,vel,dvel, debug); + const float penalty = processSample(vcand,cr/10, pos,rad,vel,dvel, minPenalty, debug); ns++; if (penalty < minPenalty) { @@ -541,4 +614,3 @@ int dtObstacleAvoidanceQuery::sampleVelocityAdaptive(const float* pos, const flo return ns; } - diff --git a/Engine/lib/recast/DetourCrowd/Source/DetourPathCorridor.cpp b/Engine/lib/recast/DetourCrowd/Source/DetourPathCorridor.cpp index a1bfe0d417..54a2ab8b81 100644 --- a/Engine/lib/recast/DetourCrowd/Source/DetourPathCorridor.cpp +++ b/Engine/lib/recast/DetourCrowd/Source/DetourPathCorridor.cpp @@ -436,7 +436,7 @@ depends on local polygon density, query search 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. */ -void dtPathCorridor::movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter) +bool dtPathCorridor::movePosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter) { dtAssert(m_path); dtAssert(m_npath); @@ -446,15 +446,19 @@ void dtPathCorridor::movePosition(const float* npos, dtNavMeshQuery* navquery, c static const int MAX_VISITED = 16; dtPolyRef visited[MAX_VISITED]; int nvisited = 0; - navquery->moveAlongSurface(m_path[0], m_pos, npos, filter, - result, visited, &nvisited, MAX_VISITED); - 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); + 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; } /** @@ -470,7 +474,7 @@ The expected use case is that the desired target will be 'near' the current corr 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. */ -void dtPathCorridor::moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter) +bool dtPathCorridor::moveTargetPosition(const float* npos, dtNavMeshQuery* navquery, const dtQueryFilter* filter) { dtAssert(m_path); dtAssert(m_npath); @@ -480,17 +484,22 @@ void dtPathCorridor::moveTargetPosition(const float* npos, dtNavMeshQuery* navqu static const int MAX_VISITED = 16; dtPolyRef visited[MAX_VISITED]; int nvisited = 0; - navquery->moveAlongSurface(m_path[m_npath-1], m_target, npos, filter, - result, visited, &nvisited, MAX_VISITED); - 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); + 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 diff --git a/Engine/lib/recast/DetourCrowd/Source/DetourPathQueue.cpp b/Engine/lib/recast/DetourCrowd/Source/DetourPathQueue.cpp index de1862ab36..1ed0cd7ebc 100644 --- a/Engine/lib/recast/DetourCrowd/Source/DetourPathQueue.cpp +++ b/Engine/lib/recast/DetourCrowd/Source/DetourPathQueue.cpp @@ -185,6 +185,7 @@ dtStatus dtPathQueue::getPathResult(dtPathQueueRef ref, dtPolyRef* path, int* pa 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; @@ -192,7 +193,7 @@ dtStatus dtPathQueue::getPathResult(dtPathQueueRef ref, dtPolyRef* path, int* pa int n = dtMin(q.npath, maxPath); memcpy(path, q.path, sizeof(dtPolyRef)*n); *pathSize = n; - return DT_SUCCESS; + return details | DT_SUCCESS; } } return DT_FAILURE; diff --git a/Engine/lib/recast/DetourCrowd/Source/DetourProximityGrid.cpp b/Engine/lib/recast/DetourCrowd/Source/DetourProximityGrid.cpp index d8226a4e5a..7af8efa018 100644 --- a/Engine/lib/recast/DetourCrowd/Source/DetourProximityGrid.cpp +++ b/Engine/lib/recast/DetourCrowd/Source/DetourProximityGrid.cpp @@ -16,11 +16,11 @@ // 3. This notice may not be removed or altered from any source distribution. // -#include #include #include #include "DetourProximityGrid.h" #include "DetourCommon.h" +#include "DetourMath.h" #include "DetourAlloc.h" #include "DetourAssert.h" @@ -47,7 +47,6 @@ inline int hashPos2(int x, int y, int n) dtProximityGrid::dtProximityGrid() : - m_maxItems(0), m_cellSize(0), m_pool(0), m_poolHead(0), @@ -103,10 +102,10 @@ void dtProximityGrid::addItem(const unsigned short id, const float minx, const float miny, const float maxx, const float maxy) { - const int iminx = (int)floorf(minx * m_invCellSize); - const int iminy = (int)floorf(miny * m_invCellSize); - const int imaxx = (int)floorf(maxx * m_invCellSize); - const int imaxy = (int)floorf(maxy * m_invCellSize); + 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); @@ -137,10 +136,10 @@ 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)floorf(minx * m_invCellSize); - const int iminy = (int)floorf(miny * m_invCellSize); - const int imaxx = (int)floorf(maxx * m_invCellSize); - const int imaxy = (int)floorf(maxy * m_invCellSize); + 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; diff --git a/Engine/lib/recast/DetourTileCache/CMakeLists.txt b/Engine/lib/recast/DetourTileCache/CMakeLists.txt deleted file mode 100644 index dd481a436e..0000000000 --- a/Engine/lib/recast/DetourTileCache/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) - -SET(detourtilecache_SRCS - Source/DetourTileCache.cpp - Source/DetourTileCacheBuilder.cpp -) - -SET(detourtilecache_HDRS - Include/DetourTileCache.h - Include/DetourTileCacheBuilder.h -) - -INCLUDE_DIRECTORIES(Include - ../Detour/Include - ../Recast/Include -) - -ADD_LIBRARY(DetourTileCache ${detourtilecache_SRCS} ${detourtilecache_HDRS}) diff --git a/Engine/lib/recast/DetourTileCache/Include/DetourTileCache.h b/Engine/lib/recast/DetourTileCache/Include/DetourTileCache.h index 21ee25e744..9c7e01c355 100644 --- a/Engine/lib/recast/DetourTileCache/Include/DetourTileCache.h +++ b/Engine/lib/recast/DetourTileCache/Include/DetourTileCache.h @@ -63,6 +63,8 @@ struct dtTileCacheParams struct dtTileCacheMeshProcess { + virtual ~dtTileCacheMeshProcess() { } + virtual void process(struct dtNavMeshCreateParams* params, unsigned char* polyAreas, unsigned short* polyFlags) = 0; }; @@ -162,7 +164,10 @@ class dtTileCache private: - + // Explicitly disabled copy constructor and copy assignment operator. + dtTileCache(const dtTileCache&); + dtTileCache& operator=(const dtTileCache&); + enum ObstacleRequestAction { REQUEST_ADD, @@ -201,7 +206,6 @@ class dtTileCache static const int MAX_UPDATE = 64; dtCompressedTileRef m_update[MAX_UPDATE]; int m_nupdate; - }; dtTileCache* dtAllocTileCache(); diff --git a/Engine/lib/recast/DetourTileCache/Include/DetourTileCacheBuilder.h b/Engine/lib/recast/DetourTileCache/Include/DetourTileCacheBuilder.h index e2b798406d..854183c913 100644 --- a/Engine/lib/recast/DetourTileCache/Include/DetourTileCacheBuilder.h +++ b/Engine/lib/recast/DetourTileCache/Include/DetourTileCacheBuilder.h @@ -78,11 +78,11 @@ struct dtTileCachePolyMesh struct dtTileCacheAlloc { - virtual void reset() - { - } + virtual ~dtTileCacheAlloc() {} + + virtual void reset() {} - virtual void* alloc(const int size) + virtual void* alloc(const size_t size) { return dtAlloc(size, DT_ALLOC_TEMP); } @@ -95,6 +95,8 @@ struct dtTileCacheAlloc struct dtTileCacheCompressor { + virtual ~dtTileCacheCompressor() { } + virtual int maxCompressedSize(const int bufferSize) = 0; virtual dtStatus compress(const unsigned char* buffer, const int bufferSize, unsigned char* compressed, const int maxCompressedSize, int* compressedSize) = 0; diff --git a/Engine/lib/recast/DetourTileCache/Source/DetourTileCache.cpp b/Engine/lib/recast/DetourTileCache/Source/DetourTileCache.cpp index 8933f6985b..9d8fac2ced 100644 --- a/Engine/lib/recast/DetourTileCache/Source/DetourTileCache.cpp +++ b/Engine/lib/recast/DetourTileCache/Source/DetourTileCache.cpp @@ -3,9 +3,9 @@ #include "DetourNavMeshBuilder.h" #include "DetourNavMesh.h" #include "DetourCommon.h" +#include "DetourMath.h" #include "DetourAlloc.h" #include "DetourAssert.h" -#include #include #include @@ -40,10 +40,10 @@ inline int computeTileHash(int x, int y, const int mask) } -struct BuildContext +struct NavMeshTileBuildContext { - inline BuildContext(struct dtTileCacheAlloc* a) : layer(0), lcset(0), lmesh(0), alloc(a) {} - inline ~BuildContext() { purge(); } + inline NavMeshTileBuildContext(struct dtTileCacheAlloc* a) : layer(0), lcset(0), lmesh(0), alloc(a) {} + inline ~NavMeshTileBuildContext() { purge(); } void purge() { dtFreeTileCacheLayer(alloc, layer); @@ -213,14 +213,14 @@ dtCompressedTile* dtTileCache::getTileAt(const int tx, const int ty, const int t dtCompressedTileRef dtTileCache::getTileRef(const dtCompressedTile* tile) const { if (!tile) return 0; - const unsigned int it = tile - m_tiles; + const unsigned int it = (unsigned int)(tile - m_tiles); return (dtCompressedTileRef)encodeTileId(tile->salt, it); } dtObstacleRef dtTileCache::getObstacleRef(const dtTileCacheObstacle* ob) const { if (!ob) return 0; - const unsigned int idx = ob - m_obstacles; + const unsigned int idx = (unsigned int)(ob - m_obstacles); return encodeObstacleId(ob->salt, idx); } @@ -350,7 +350,7 @@ dtStatus dtTileCache::removeTile(dtCompressedTileRef ref, unsigned char** data, } -dtObstacleRef dtTileCache::addObstacle(const float* pos, const float radius, const float height, dtObstacleRef* result) +dtStatus dtTileCache::addObstacle(const float* pos, const float radius, const float height, dtObstacleRef* result) { if (m_nreqs >= MAX_REQUESTS) return DT_FAILURE | DT_BUFFER_TOO_SMALL; @@ -384,7 +384,7 @@ dtObstacleRef dtTileCache::addObstacle(const float* pos, const float radius, con return DT_SUCCESS; } -dtObstacleRef dtTileCache::removeObstacle(const dtObstacleRef ref) +dtStatus dtTileCache::removeObstacle(const dtObstacleRef ref) { if (!ref) return DT_SUCCESS; @@ -409,10 +409,10 @@ dtStatus dtTileCache::queryTiles(const float* bmin, const float* bmax, const float tw = m_params.width * m_params.cs; const float th = m_params.height * m_params.cs; - const int tx0 = (int)floorf((bmin[0]-m_params.orig[0]) / tw); - const int tx1 = (int)floorf((bmax[0]-m_params.orig[0]) / tw); - const int ty0 = (int)floorf((bmin[2]-m_params.orig[2]) / th); - const int ty1 = (int)floorf((bmax[2]-m_params.orig[2]) / th); + const int tx0 = (int)dtMathFloorf((bmin[0]-m_params.orig[0]) / tw); + const int tx1 = (int)dtMathFloorf((bmax[0]-m_params.orig[0]) / tw); + const int ty0 = (int)dtMathFloorf((bmin[2]-m_params.orig[2]) / th); + const int ty1 = (int)dtMathFloorf((bmax[2]-m_params.orig[2]) / th); for (int ty = ty0; ty <= ty1; ++ty) { @@ -587,7 +587,7 @@ dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* m_talloc->reset(); - BuildContext bc(m_talloc); + NavMeshTileBuildContext bc(m_talloc); const int walkableClimbVx = (int)(m_params.walkableClimb / m_params.ch); dtStatus status; @@ -631,7 +631,11 @@ dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* // Early out if the mesh tile is empty. if (!bc.lmesh->npolys) + { + // Remove existing tile. + navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0); return DT_SUCCESS; + } dtNavMeshCreateParams params; memset(¶ms, 0, sizeof(params)); diff --git a/Engine/lib/recast/DetourTileCache/Source/DetourTileCacheBuilder.cpp b/Engine/lib/recast/DetourTileCache/Source/DetourTileCacheBuilder.cpp index ca336a0e89..2c82cf0a9f 100644 --- a/Engine/lib/recast/DetourTileCache/Source/DetourTileCacheBuilder.cpp +++ b/Engine/lib/recast/DetourTileCache/Source/DetourTileCacheBuilder.cpp @@ -17,11 +17,11 @@ // #include "DetourCommon.h" +#include "DetourMath.h" #include "DetourStatus.h" #include "DetourAssert.h" #include "DetourTileCacheBuilder.h" #include -#include template class dtFixedArray @@ -1068,6 +1068,7 @@ static bool buildMeshAdjacency(dtTileCacheAlloc* alloc, } +// Last time I checked the if version got compiled using cmov, which was a lot faster than module (with idiv). inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; } inline int next(int i, int n) { return i+1 < n ? i+1 : 0; } @@ -1968,12 +1969,12 @@ dtStatus dtMarkCylinderArea(dtTileCacheLayer& layer, const float* orig, const fl const float px = (pos[0]-orig[0])*ics; const float pz = (pos[2]-orig[2])*ics; - int minx = (int)floorf((bmin[0]-orig[0])*ics); - int miny = (int)floorf((bmin[1]-orig[1])*ich); - int minz = (int)floorf((bmin[2]-orig[2])*ics); - int maxx = (int)floorf((bmax[0]-orig[0])*ics); - int maxy = (int)floorf((bmax[1]-orig[1])*ich); - int maxz = (int)floorf((bmax[2]-orig[2])*ics); + int minx = (int)dtMathFloorf((bmin[0]-orig[0])*ics); + int miny = (int)dtMathFloorf((bmin[1]-orig[1])*ich); + int minz = (int)dtMathFloorf((bmin[2]-orig[2])*ics); + int maxx = (int)dtMathFloorf((bmax[0]-orig[0])*ics); + int maxy = (int)dtMathFloorf((bmax[1]-orig[1])*ich); + int maxz = (int)dtMathFloorf((bmax[2]-orig[2])*ics); if (maxx < 0) return DT_SUCCESS; if (minx >= w) return DT_SUCCESS; @@ -2116,6 +2117,7 @@ dtStatus dtDecompressTileCacheLayer(dtTileCacheAlloc* alloc, dtTileCacheCompress bool dtTileCacheHeaderSwapEndian(unsigned char* data, const int dataSize) { + dtIgnoreUnused(dataSize); dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)data; int swappedMagic = DT_TILECACHE_MAGIC; diff --git a/Engine/lib/recast/Old Release Notes.txt b/Engine/lib/recast/Old Release Notes.txt new file mode 100644 index 0000000000..0c2f7b1675 --- /dev/null +++ b/Engine/lib/recast/Old Release Notes.txt @@ -0,0 +1,120 @@ + +Recast & Detour Version 1.4 + + +Recast + +Recast is state of the art navigation mesh construction toolset for games. + + * It is automatic, which means that you can throw any level geometry + at it and you will get robust mesh out + * It is fast which means swift turnaround times for level designers + * It is open source so it comes with full source and you can + customize it to your hearts content. + +The Recast process starts with constructing a voxel mold from a level geometry +and then casting a navigation mesh over it. The process consists of three steps, +building the voxel mold, partitioning the mold into simple regions, peeling off +the regions as simple polygons. + + 1. The voxel mold is build from the input triangle mesh by rasterizing + the triangles into a multi-layer heightfield. Some simple filters are + then applied to the mold to prune out locations where the character + would not be able to move. + 2. The walkable areas described by the mold are divided into simple + overlayed 2D regions. The resulting regions have only one non-overlapping + contour, which simplifies the final step of the process tremendously. + 3. The navigation polygons are peeled off from the regions by first tracing + the boundaries and then simplifying them. The resulting polygons are + finally converted to convex polygons which makes them perfect for + pathfinding and spatial reasoning about the level. + +The toolset code is located in the Recast folder and demo application using the Recast +toolset is located in the RecastDemo folder. + +The project files with this distribution can be compiled with Microsoft Visual C++ 2008 +(you can download it for free) and XCode 3.1. + + +Detour + +Recast is accompanied with Detour, path-finding and spatial reasoning toolkit. You can use any navigation mesh with Detour, but of course the data generated with Recast fits perfectly. + +Detour offers simple static navigation mesh which is suitable for many simple cases, as well as tiled navigation mesh which allows you to plug in and out pieces of the mesh. The tiled mesh allows to create systems where you stream new navigation data in and out as the player progresses the level, or you may regenerate tiles as the world changes. + + +Latest code available at http://code.google.com/p/recastnavigation/ + + +-- + +Release Notes + +---------------- +* Recast 1.4 + Released August 24th, 2009 + +- Added detail height mesh generation (RecastDetailMesh.cpp) for single, + tiled statmeshes as well as tilemesh. +- Added feature to contour tracing which detects extra vertices along + tile edges which should be removed later. +- Changed the tiled stat mesh preprocess, so that it first generated + polymeshes per tile and finally combines them. +- Fixed bug in the GUI code where invisible buttons could be pressed. + +---------------- +* Recast 1.31 + Released July 24th, 2009 + +- Better cost and heuristic functions. +- Fixed tile navmesh raycast on tile borders. + +---------------- +* Recast 1.3 + Released July 14th, 2009 + +- Added dtTileNavMesh which allows to dynamically add and remove navmesh pieces at runtime. +- Renamed stat navmesh types to dtStat* (i.e. dtPoly is now dtStatPoly). +- Moved common code used by tile and stat navmesh to DetourNode.h/cpp and DetourCommon.h/cpp. +- Refactores the demo code. + +---------------- +* Recast 1.2 + Released June 17th, 2009 + +- Added tiled mesh generation. The tiled generation allows to generate navigation for + much larger worlds, it removes some of the artifacts that comes from distance fields + in open areas, and allows later streaming and dynamic runtime generation +- Improved and added some debug draw modes +- API change: The helper function rcBuildNavMesh does not exists anymore, + had to change few internal things to cope with the tiled processing, + similar API functionality will be added later once the tiled process matures +- The demo is getting way too complicated, need to split demos +- Fixed several filtering functions so that the mesh is tighter to the geometry, + sometimes there could be up error up to tow voxel units close to walls, + now it should be just one. + +---------------- +* Recast 1.1 + Released April 11th, 2009 + +This is the first release of Detour. + +---------------- +* Recast 1.0 + Released March 29th, 2009 + +This is the first release of Recast. + +The process is not always as robust as I would wish. The watershed phase sometimes swallows tiny islands +which are close to edges. These droppings are handled in rcBuildContours, but the code is not +particularly robust either. + +Another non-robust case is when portal contours (contours shared between two regions) are always +assumed to be straight. That can lead to overlapping contours specially when the level has +large open areas. + + + +Mikko Mononen +memon@inside.org diff --git a/Engine/lib/recast/Readme.txt b/Engine/lib/recast/Readme.txt index 0c2f7b1675..bb9808ff23 100644 --- a/Engine/lib/recast/Readme.txt +++ b/Engine/lib/recast/Readme.txt @@ -1,120 +1,42 @@ -Recast & Detour Version 1.4 +Recast & Detour +=============== - -Recast +## Recast Recast is state of the art navigation mesh construction toolset for games. - * It is automatic, which means that you can throw any level geometry - at it and you will get robust mesh out - * It is fast which means swift turnaround times for level designers - * It is open source so it comes with full source and you can - customize it to your hearts content. +* It is automatic, which means that you can throw any level geometry at it and you will get robust mesh out +* It is fast which means swift turnaround times for level designers +* It is open source so it comes with full source and you can customize it to your heart's content. The Recast process starts with constructing a voxel mold from a level geometry and then casting a navigation mesh over it. The process consists of three steps, building the voxel mold, partitioning the mold into simple regions, peeling off the regions as simple polygons. - 1. The voxel mold is build from the input triangle mesh by rasterizing - the triangles into a multi-layer heightfield. Some simple filters are - then applied to the mold to prune out locations where the character - would not be able to move. - 2. The walkable areas described by the mold are divided into simple - overlayed 2D regions. The resulting regions have only one non-overlapping - contour, which simplifies the final step of the process tremendously. - 3. The navigation polygons are peeled off from the regions by first tracing - the boundaries and then simplifying them. The resulting polygons are - finally converted to convex polygons which makes them perfect for - pathfinding and spatial reasoning about the level. - -The toolset code is located in the Recast folder and demo application using the Recast -toolset is located in the RecastDemo folder. - -The project files with this distribution can be compiled with Microsoft Visual C++ 2008 -(you can download it for free) and XCode 3.1. +1. The voxel mold is build from the input triangle mesh by rasterizing the triangles into a multi-layer heightfield. Some simple filters are then applied to the mold to prune out locations where the character would not be able to move. +2. The walkable areas described by the mold are divided into simple overlayed 2D regions. The resulting regions have only one non-overlapping contour, which simplifies the final step of the process tremendously. +3. The navigation polygons are peeled off from the regions by first tracing the boundaries and then simplifying them. The resulting polygons are finally converted to convex polygons which makes them perfect for pathfinding and spatial reasoning about the level. - -Detour +## Detour Recast is accompanied with Detour, path-finding and spatial reasoning toolkit. You can use any navigation mesh with Detour, but of course the data generated with Recast fits perfectly. -Detour offers simple static navigation mesh which is suitable for many simple cases, as well as tiled navigation mesh which allows you to plug in and out pieces of the mesh. The tiled mesh allows to create systems where you stream new navigation data in and out as the player progresses the level, or you may regenerate tiles as the world changes. - - -Latest code available at http://code.google.com/p/recastnavigation/ - - --- - -Release Notes - ----------------- -* Recast 1.4 - Released August 24th, 2009 - -- Added detail height mesh generation (RecastDetailMesh.cpp) for single, - tiled statmeshes as well as tilemesh. -- Added feature to contour tracing which detects extra vertices along - tile edges which should be removed later. -- Changed the tiled stat mesh preprocess, so that it first generated - polymeshes per tile and finally combines them. -- Fixed bug in the GUI code where invisible buttons could be pressed. - ----------------- -* Recast 1.31 - Released July 24th, 2009 - -- Better cost and heuristic functions. -- Fixed tile navmesh raycast on tile borders. - ----------------- -* Recast 1.3 - Released July 14th, 2009 - -- Added dtTileNavMesh which allows to dynamically add and remove navmesh pieces at runtime. -- Renamed stat navmesh types to dtStat* (i.e. dtPoly is now dtStatPoly). -- Moved common code used by tile and stat navmesh to DetourNode.h/cpp and DetourCommon.h/cpp. -- Refactores the demo code. - ----------------- -* Recast 1.2 - Released June 17th, 2009 - -- Added tiled mesh generation. The tiled generation allows to generate navigation for - much larger worlds, it removes some of the artifacts that comes from distance fields - in open areas, and allows later streaming and dynamic runtime generation -- Improved and added some debug draw modes -- API change: The helper function rcBuildNavMesh does not exists anymore, - had to change few internal things to cope with the tiled processing, - similar API functionality will be added later once the tiled process matures -- The demo is getting way too complicated, need to split demos -- Fixed several filtering functions so that the mesh is tighter to the geometry, - sometimes there could be up error up to tow voxel units close to walls, - now it should be just one. - ----------------- -* Recast 1.1 - Released April 11th, 2009 - -This is the first release of Detour. +Detour offers simple static navigation mesh which is suitable for many simple cases, as well as tiled navigation mesh which allows you to plug in and out pieces of the mesh. The tiled mesh allows you to create systems where you stream new navigation data in and out as the player progresses the level, or you may regenerate tiles as the world changes. ----------------- -* Recast 1.0 - Released March 29th, 2009 +## Integrating with your own project -This is the first release of Recast. +It is recommended to add the source directories `DebugUtils`, `Detour`, `DetourCrowd`, `DetourTileCache`, and `Recast` into your own project depending on which parts of the project you need. For example your level building tool could include DebugUtils, Recast, and Detour, and your game runtime could just include Detour. -The process is not always as robust as I would wish. The watershed phase sometimes swallows tiny islands -which are close to edges. These droppings are handled in rcBuildContours, but the code is not -particularly robust either. +## Discuss -Another non-robust case is when portal contours (contours shared between two regions) are always -assumed to be straight. That can lead to overlapping contours specially when the level has -large open areas. +- Discuss Recast & Detour: http://groups.google.com/group/recastnavigation +- Development blog: http://digestingduck.blogspot.com/ +## License +Recast & Detour is licensed under ZLib license, see License.txt for more information. -Mikko Mononen -memon@inside.org +## Download +Latest code available at https://github.com/recastnavigation/recastnavigation diff --git a/Engine/lib/recast/Recast/CMakeLists.txt b/Engine/lib/recast/Recast/CMakeLists.txt deleted file mode 100644 index 202304897f..0000000000 --- a/Engine/lib/recast/Recast/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) - -SET(recast_SRCS - Source/Recast.cpp - Source/RecastArea.cpp - Source/RecastAlloc.cpp - Source/RecastContour.cpp - Source/RecastFilter.cpp - Source/RecastLayers.cpp - Source/RecastMesh.cpp - Source/RecastMeshDetail.cpp - Source/RecastRasterization.cpp - Source/RecastRegion.cpp -) - -SET(recast_HDRS - Include/Recast.h - Include/RecastAlloc.h - Include/RecastAssert.h -) - -INCLUDE_DIRECTORIES(Include) - -ADD_LIBRARY(Recast ${recast_SRCS} ${recast_HDRS}) diff --git a/Engine/lib/recast/Recast/Include/Recast.h b/Engine/lib/recast/Recast/Include/Recast.h index 1ea40a3f41..2adcdcb343 100644 --- a/Engine/lib/recast/Recast/Include/Recast.h +++ b/Engine/lib/recast/Recast/Include/Recast.h @@ -127,7 +127,7 @@ class rcContext inline void resetTimers() { if (m_timerEnabled) doResetTimers(); } /// Starts the specified performance timer. - /// @param label The category of timer. + /// @param label The category of the timer. inline void startTimer(const rcTimerLabel label) { if (m_timerEnabled) doStartTimer(label); } /// Stops the specified performance timer. @@ -173,6 +173,26 @@ class rcContext bool m_timerEnabled; }; +/// A helper to first start a timer and then stop it when this helper goes out of scope. +/// @see rcContext +class rcScopedTimer +{ +public: + /// Constructs an instance and starts the timer. + /// @param[in] ctx The context to use. + /// @param[in] label The category of the timer. + inline rcScopedTimer(rcContext* ctx, const rcTimerLabel label) : m_ctx(ctx), m_label(label) { m_ctx->startTimer(m_label); } + inline ~rcScopedTimer() { m_ctx->stopTimer(m_label); } + +private: + // Explicitly disabled copy constructor and copy assignment operator. + rcScopedTimer(const rcScopedTimer&); + rcScopedTimer& operator=(const rcScopedTimer&); + + rcContext* const m_ctx; + const rcTimerLabel m_label; +}; + /// Specifies a configuration to use when performing Recast builds. /// @ingroup recast struct rcConfig @@ -219,7 +239,7 @@ struct rcConfig int maxEdgeLen; /// The maximum distance a simplfied contour's border edges should deviate - /// the original raw contour. [Limit: >=0] [Units: wu] + /// the original raw contour. [Limit: >=0] [Units: vx] float maxSimplificationError; /// The minimum number of cells allowed to form isolated island areas. [Limit: >=0] [Units: vx] @@ -245,7 +265,7 @@ struct rcConfig /// Defines the number of bits allocated to rcSpan::smin and rcSpan::smax. static const int RC_SPAN_HEIGHT_BITS = 13; /// Defines the maximum value for rcSpan::smin and rcSpan::smax. -static const int RC_SPAN_MAX_HEIGHT = (1< void rcIgnoreUnused(const T&) { } + /// Swaps the values of the two parameters. /// @param[in,out] a Value A /// @param[in,out] b Value B @@ -742,6 +777,7 @@ void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* /// @param[in] bmax The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] /// @param[in] cs The xz-plane cell size to use for the field. [Limit: > 0] [Units: wu] /// @param[in] ch The y-axis cell size to use for field. [Limit: > 0] [Units: wu] +/// @returns True if the operation completed successfully. bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, const float* bmin, const float* bmax, float cs, float ch); @@ -785,7 +821,8 @@ void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, /// @param[in] smax The maximum height of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT] [Units: vx] /// @param[in] area The area id of the span. [Limit: <= #RC_WALKABLE_AREA) /// @param[in] flagMergeThr The merge theshold. [Limit: >= 0] [Units: vx] -void rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, +/// @returns True if the operation completed successfully. +bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, const unsigned short smin, const unsigned short smax, const unsigned char area, const int flagMergeThr); @@ -799,7 +836,8 @@ void rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, /// @param[in,out] solid An initialized heightfield. /// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. /// [Limit: >= 0] [Units: vx] -void rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, const unsigned char area, rcHeightfield& solid, const int flagMergeThr = 1); @@ -814,7 +852,8 @@ void rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const /// @param[in,out] solid An initialized heightfield. /// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. /// [Limit: >= 0] [Units: vx] -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, const int* tris, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr = 1); @@ -829,7 +868,8 @@ void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, /// @param[in,out] solid An initialized heightfield. /// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. /// [Limit: >= 0] [Units: vx] -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, const unsigned short* tris, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr = 1); @@ -842,7 +882,8 @@ void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int nv, /// @param[in,out] solid An initialized heightfield. /// @param[in] flagMergeThr The distance where the walkable flag is favored over the non-walkable flag. /// [Limit: >= 0] [Units: vx] -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, +/// @returns True if the operation completed successfully. +bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr = 1); /// Marks non-walkable spans as walkable if their maximum is within @p walkableClimp of a walkable neihbor. @@ -964,7 +1005,7 @@ void rcMarkCylinderArea(rcContext* ctx, const float* pos, /// @returns True if the operation completed successfully. bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf); -/// Builds region data for the heightfield using watershed partitioning. +/// Builds region data for the heightfield using watershed partitioning. /// @ingroup recast /// @param[in,out] ctx The build context to use during the operation. /// @param[in,out] chf A populated compact heightfield. @@ -978,6 +1019,18 @@ bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf); bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int minRegionArea, const int mergeRegionArea); +/// Builds region data for the heightfield by partitioning the heightfield in non-overlapping layers. +/// @ingroup recast +/// @param[in,out] ctx The build context to use during the operation. +/// @param[in,out] chf A populated compact heightfield. +/// @param[in] borderSize The size of the non-navigable border around the heightfield. +/// [Limit: >=0] [Units: vx] +/// @param[in] minRegionArea The minimum number of cells allowed to form isolated island areas. +/// [Limit: >=0] [Units: vx]. +/// @returns True if the operation completed successfully. +bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, + const int borderSize, const int minRegionArea); + /// Builds region data for the heightfield using simple monotone partitioning. /// @ingroup recast /// @param[in,out] ctx The build context to use during the operation. @@ -992,7 +1045,6 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int minRegionArea, const int mergeRegionArea); - /// Sets the neighbor connection data for the specified direction. /// @param[in] s The span to update. /// @param[in] dir The direction to set. [Limits: 0 <= value < 4] @@ -1021,7 +1073,7 @@ inline int rcGetCon(const rcCompactSpan& s, int dir) /// in the direction. inline int rcGetDirOffsetX(int dir) { - const int offset[4] = { -1, 0, 1, 0, }; + static const int offset[4] = { -1, 0, 1, 0, }; return offset[dir&0x03]; } @@ -1031,10 +1083,20 @@ inline int rcGetDirOffsetX(int dir) /// in the direction. inline int rcGetDirOffsetY(int dir) { - const int offset[4] = { 0, 1, 0, -1 }; + static const int offset[4] = { 0, 1, 0, -1 }; return offset[dir&0x03]; } +/// Gets the direction for the specified offset. One of x and y should be 0. +/// @param[in] x The x offset. [Limits: -1 <= value <= 1] +/// @param[in] y The y offset. [Limits: -1 <= value <= 1] +/// @return The direction that represents the offset. +inline int rcGetDirForOffset(int x, int y) +{ + static const int dirs[5] = { 3, 0, -1, 2, 1 }; + return dirs[((y+1)<<1)+x]; +} + /// @} /// @name Layer, Contour, Polymesh, and Detail Mesh Functions /// @see rcHeightfieldLayer, rcContourSet, rcPolyMesh, rcPolyMeshDetail @@ -1067,7 +1129,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, /// @returns True if the operation completed successfully. bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, const float maxError, const int maxEdgeLen, - rcContourSet& cset, const int flags = RC_CONTOUR_TESS_WALL_EDGES); + rcContourSet& cset, const int buildFlags = RC_CONTOUR_TESS_WALL_EDGES); /// Builds a polygon mesh from the provided contours. /// @ingroup recast diff --git a/Engine/lib/recast/Recast/Include/RecastAlloc.h b/Engine/lib/recast/Recast/Include/RecastAlloc.h index 438be9ea56..f1608fb553 100644 --- a/Engine/lib/recast/Recast/Include/RecastAlloc.h +++ b/Engine/lib/recast/Recast/Include/RecastAlloc.h @@ -19,6 +19,8 @@ #ifndef RECASTALLOC_H #define RECASTALLOC_H +#include + /// Provides hint values to the memory allocator on how long the /// memory is expected to be used. enum rcAllocHint @@ -32,7 +34,7 @@ enum rcAllocHint // @param[in] rcAllocHint A hint to the allocator on how long the memory is expected to be in use. // @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. /// @see rcAllocSetCustom -typedef void* (rcAllocFunc)(int size, rcAllocHint hint); +typedef void* (rcAllocFunc)(size_t size, rcAllocHint hint); /// A memory deallocation function. /// @param[in] ptr A pointer to a memory block previously allocated using #rcAllocFunc. @@ -49,7 +51,7 @@ void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc); /// @param[in] hint A hint to the allocator on how long the memory is expected to be in use. /// @return A pointer to the beginning of the allocated memory block, or null if the allocation failed. /// @see rcFree -void* rcAlloc(int size, rcAllocHint hint); +void* rcAlloc(size_t size, rcAllocHint hint); /// Deallocates a memory block. /// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc. @@ -62,42 +64,58 @@ class rcIntArray { int* m_data; int m_size, m_cap; - inline rcIntArray(const rcIntArray&); - inline rcIntArray& operator=(const rcIntArray&); -public: + void doResize(int n); + + // Explicitly disabled copy constructor and copy assignment operator. + rcIntArray(const rcIntArray&); + rcIntArray& operator=(const rcIntArray&); + +public: /// Constructs an instance with an initial array size of zero. - inline rcIntArray() : m_data(0), m_size(0), m_cap(0) {} + rcIntArray() : m_data(0), m_size(0), m_cap(0) {} /// Constructs an instance initialized to the specified size. /// @param[in] n The initial size of the integer array. - inline rcIntArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); } - inline ~rcIntArray() { rcFree(m_data); } + rcIntArray(int n) : m_data(0), m_size(0), m_cap(0) { resize(n); } + ~rcIntArray() { rcFree(m_data); } /// Specifies the new size of the integer array. /// @param[in] n The new size of the integer array. - void resize(int n); + void resize(int n) + { + if (n > m_cap) + doResize(n); + + m_size = n; + } /// Push the specified integer onto the end of the array and increases the size by one. /// @param[in] item The new value. - inline void push(int item) { resize(m_size+1); m_data[m_size-1] = item; } + void push(int item) { resize(m_size+1); m_data[m_size-1] = item; } /// Returns the value at the end of the array and reduces the size by one. /// @return The value at the end of the array. - inline int pop() { if (m_size > 0) m_size--; return m_data[m_size]; } + int pop() + { + if (m_size > 0) + m_size--; + + return m_data[m_size]; + } /// The value at the specified array index. /// @warning Does not provide overflow protection. /// @param[in] i The index of the value. - inline const int& operator[](int i) const { return m_data[i]; } + const int& operator[](int i) const { return m_data[i]; } /// The value at the specified array index. /// @warning Does not provide overflow protection. /// @param[in] i The index of the value. - inline int& operator[](int i) { return m_data[i]; } + int& operator[](int i) { return m_data[i]; } /// The current size of the integer array. - inline int size() const { return m_size; } + int size() const { return m_size; } }; /// A simple helper class used to delete an array when it goes out of scope. @@ -119,6 +137,11 @@ template class rcScopedDelete /// The root array pointer. /// @return The root array pointer. inline operator T*() { return ptr; } + +private: + // Explicitly disabled copy constructor and copy assignment operator. + rcScopedDelete(const rcScopedDelete&); + rcScopedDelete& operator=(const rcScopedDelete&); }; #endif diff --git a/Engine/lib/recast/Recast/Source/Recast.cpp b/Engine/lib/recast/Recast/Source/Recast.cpp index 803daac3bc..46bc8b7810 100644 --- a/Engine/lib/recast/Recast/Source/Recast.cpp +++ b/Engine/lib/recast/Recast/Source/Recast.cpp @@ -208,12 +208,11 @@ void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocHeightfield, rcHeightfield -bool rcCreateHeightfield(rcContext* /*ctx*/, rcHeightfield& hf, int width, int height, +bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height, const float* bmin, const float* bmax, float cs, float ch) { - // TODO: VC complains about unref formal variable, figure out a way to handle this better. -// rcAssert(ctx); + rcIgnoreUnused(ctx); hf.width = width; hf.height = height; @@ -239,19 +238,18 @@ static void calcTriNormal(const float* v0, const float* v1, const float* v2, flo /// @par /// -/// Only sets the aread id's for the walkable triangles. Does not alter the +/// Only sets the area id's for the walkable triangles. Does not alter the /// area id's for unwalkable triangles. /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcMarkWalkableTriangles(rcContext* /*ctx*/, const float walkableSlopeAngle, +void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int /*nv*/, const int* tris, int nt, unsigned char* areas) { - // TODO: VC complains about unref formal variable, figure out a way to handle this better. -// rcAssert(ctx); + rcIgnoreUnused(ctx); const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); @@ -269,19 +267,18 @@ void rcMarkWalkableTriangles(rcContext* /*ctx*/, const float walkableSlopeAngle, /// @par /// -/// Only sets the aread id's for the unwalkable triangles. Does not alter the +/// Only sets the area id's for the unwalkable triangles. Does not alter the /// area id's for walkable triangles. /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles -void rcClearUnwalkableTriangles(rcContext* /*ctx*/, const float walkableSlopeAngle, +void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int /*nv*/, const int* tris, int nt, unsigned char* areas) { - // TODO: VC complains about unref formal variable, figure out a way to handle this better. -// rcAssert(ctx); + rcIgnoreUnused(ctx); const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI); @@ -297,10 +294,9 @@ void rcClearUnwalkableTriangles(rcContext* /*ctx*/, const float walkableSlopeAng } } -int rcGetHeightFieldSpanCount(rcContext* /*ctx*/, rcHeightfield& hf) +int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf) { - // TODO: VC complains about unref formal variable, figure out a way to handle this better. -// rcAssert(ctx); + rcIgnoreUnused(ctx); const int w = hf.width; const int h = hf.height; @@ -322,7 +318,7 @@ int rcGetHeightFieldSpanCount(rcContext* /*ctx*/, rcHeightfield& hf) /// @par /// /// This is just the beginning of the process of fully building a compact heightfield. -/// Various filters may be applied applied, then the distance field and regions built. +/// Various filters may be applied, then the distance field and regions built. /// E.g: #rcBuildDistanceField and #rcBuildRegions /// /// See the #rcConfig documentation for more information on the configuration parameters. @@ -333,7 +329,7 @@ bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const i { rcAssert(ctx); - ctx->startTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); + rcScopedTimer timer(ctx, RC_TIMER_BUILD_COMPACTHEIGHTFIELD); const int w = hf.width; const int h = hf.height; @@ -460,8 +456,6 @@ bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const i ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", tooHighNeighbour, MAX_LAYERS); } - - ctx->stopTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); return true; } @@ -490,4 +484,4 @@ static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf) size += sizeof(rcCompactCell) * chf.width * chf.height; return size; } -*/ \ No newline at end of file +*/ diff --git a/Engine/lib/recast/Recast/Source/RecastAlloc.cpp b/Engine/lib/recast/Recast/Source/RecastAlloc.cpp index b5ec151614..ee1039f2f4 100644 --- a/Engine/lib/recast/Recast/Source/RecastAlloc.cpp +++ b/Engine/lib/recast/Recast/Source/RecastAlloc.cpp @@ -20,7 +20,7 @@ #include #include "RecastAlloc.h" -static void *rcAllocDefault(int size, rcAllocHint) +static void *rcAllocDefault(size_t size, rcAllocHint) { return malloc(size); } @@ -41,7 +41,7 @@ void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc) } /// @see rcAllocSetCustom -void* rcAlloc(int size, rcAllocHint hint) +void* rcAlloc(size_t size, rcAllocHint hint) { return sRecastAllocFunc(size, hint); } @@ -72,17 +72,13 @@ void rcFree(void* ptr) /// Using this method ensures the array is at least large enough to hold /// the specified number of elements. This can improve performance by /// avoiding auto-resizing during use. -void rcIntArray::resize(int n) +void rcIntArray::doResize(int n) { - if (n > m_cap) - { - if (!m_cap) m_cap = n; - while (m_cap < n) m_cap *= 2; - int* newData = (int*)rcAlloc(m_cap*sizeof(int), RC_ALLOC_TEMP); - if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int)); - rcFree(m_data); - m_data = newData; - } - m_size = n; + if (!m_cap) m_cap = n; + while (m_cap < n) m_cap *= 2; + int* newData = (int*)rcAlloc(m_cap*sizeof(int), RC_ALLOC_TEMP); + if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int)); + rcFree(m_data); + m_data = newData; } diff --git a/Engine/lib/recast/Recast/Source/RecastArea.cpp b/Engine/lib/recast/Recast/Source/RecastArea.cpp index 1a338cd9b8..97139cf996 100644 --- a/Engine/lib/recast/Recast/Source/RecastArea.cpp +++ b/Engine/lib/recast/Recast/Source/RecastArea.cpp @@ -41,7 +41,7 @@ bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf) const int w = chf.width; const int h = chf.height; - ctx->startTimer(RC_TIMER_ERODE_AREA); + rcScopedTimer timer(ctx, RC_TIMER_ERODE_AREA); unsigned char* dist = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); if (!dist) @@ -215,8 +215,6 @@ bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf) rcFree(dist); - ctx->stopTimer(RC_TIMER_ERODE_AREA); - return true; } @@ -245,7 +243,7 @@ bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf) const int w = chf.width; const int h = chf.height; - ctx->startTimer(RC_TIMER_MEDIAN_AREA); + rcScopedTimer timer(ctx, RC_TIMER_MEDIAN_AREA); unsigned char* areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); if (!areas) @@ -306,8 +304,6 @@ bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf) memcpy(chf.areas, areas, sizeof(unsigned char)*chf.spanCount); rcFree(areas); - - ctx->stopTimer(RC_TIMER_MEDIAN_AREA); return true; } @@ -322,7 +318,7 @@ void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigne { rcAssert(ctx); - ctx->startTimer(RC_TIMER_MARK_BOX_AREA); + rcScopedTimer timer(ctx, RC_TIMER_MARK_BOX_AREA); int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs); int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch); @@ -357,9 +353,6 @@ void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigne } } } - - ctx->stopTimer(RC_TIMER_MARK_BOX_AREA); - } @@ -391,7 +384,7 @@ void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, { rcAssert(ctx); - ctx->startTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); + rcScopedTimer timer(ctx, RC_TIMER_MARK_CONVEXPOLY_AREA); float bmin[3], bmax[3]; rcVcopy(bmin, verts); @@ -448,8 +441,6 @@ void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, } } } - - ctx->stopTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); } int rcOffsetPoly(const float* verts, const int nverts, const float offset, @@ -541,7 +532,7 @@ void rcMarkCylinderArea(rcContext* ctx, const float* pos, { rcAssert(ctx); - ctx->startTimer(RC_TIMER_MARK_CYLINDER_AREA); + rcScopedTimer timer(ctx, RC_TIMER_MARK_CYLINDER_AREA); float bmin[3], bmax[3]; bmin[0] = pos[0] - r; @@ -597,6 +588,4 @@ void rcMarkCylinderArea(rcContext* ctx, const float* pos, } } } - - ctx->stopTimer(RC_TIMER_MARK_CYLINDER_AREA); } diff --git a/Engine/lib/recast/Recast/Source/RecastContour.cpp b/Engine/lib/recast/Recast/Source/RecastContour.cpp index 5c324bcedf..277ab01501 100644 --- a/Engine/lib/recast/Recast/Source/RecastContour.cpp +++ b/Engine/lib/recast/Recast/Source/RecastContour.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "Recast.h" #include "RecastAlloc.h" #include "RecastAssert.h" @@ -36,7 +37,7 @@ static int getCornerHeight(int x, int y, int i, int dir, unsigned int regs[4] = {0,0,0,0}; // Combine region and area codes in order to prevent - // border vertices which are in between two areas to be removed. + // border vertices which are in between two areas to be removed. regs[0] = chf.spans[i].reg | (chf.areas[i] << 16); if (rcGetCon(s, dir) != RC_NOT_CONNECTED) @@ -187,27 +188,6 @@ static float distancePtSeg(const int x, const int z, const int px, const int pz, const int qx, const int qz) { -/* float pqx = (float)(qx - px); - float pqy = (float)(qy - py); - float pqz = (float)(qz - pz); - float dx = (float)(x - px); - float dy = (float)(y - py); - float dz = (float)(z - pz); - float d = pqx*pqx + pqy*pqy + pqz*pqz; - float t = pqx*dx + pqy*dy + pqz*dz; - if (d > 0) - t /= d; - if (t < 0) - t = 0; - else if (t > 1) - t = 1; - - dx = px + t*pqx - x; - dy = py + t*pqy - y; - dz = pz + t*pqz - z; - - return dx*dx + dy*dy + dz*dz;*/ - float pqx = (float)(qx - px); float pqz = (float)(qz - pz); float dx = (float)(x - px); @@ -257,13 +237,13 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, simplified.push(points[i*4+2]); simplified.push(i); } - } + } } if (simplified.size() == 0) { // If there is no connections at all, - // create some initial points for the simplification process. + // create some initial points for the simplification process. // Find lower-left and upper-right vertices of the contour. int llx = points[0]; int lly = points[1]; @@ -311,19 +291,19 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, { int ii = (i+1) % (simplified.size()/4); - const int ax = simplified[i*4+0]; - const int az = simplified[i*4+2]; - const int ai = simplified[i*4+3]; - - const int bx = simplified[ii*4+0]; - const int bz = simplified[ii*4+2]; - const int bi = simplified[ii*4+3]; + int ax = simplified[i*4+0]; + int az = simplified[i*4+2]; + int ai = simplified[i*4+3]; + + int bx = simplified[ii*4+0]; + int bz = simplified[ii*4+2]; + int bi = simplified[ii*4+3]; // Find maximum deviation from the segment. float maxd = 0; int maxi = -1; int ci, cinc, endi; - + // Traverse the segment in lexilogical order so that the // max deviation is calculated similarly when traversing // opposite segments. @@ -338,6 +318,8 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, cinc = pn-1; ci = (bi+cinc) % pn; endi = ai; + rcSwap(ax, bx); + rcSwap(az, bz); } // Tessellate only outer edges or edges between areas. @@ -397,11 +379,11 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, const int bx = simplified[ii*4+0]; const int bz = simplified[ii*4+2]; const int bi = simplified[ii*4+3]; - + // Find maximum deviation from the segment. int maxi = -1; int ci = (ai+1) % pn; - + // Tessellate only outer edges or edges between areas. bool tess = false; // Wall edges. @@ -469,32 +451,6 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, } -static void removeDegenerateSegments(rcIntArray& simplified) -{ - // Remove adjacent vertices which are equal on xz-plane, - // or else the triangulator will get confused. - for (int i = 0; i < simplified.size()/4; ++i) - { - int ni = i+1; - if (ni >= (simplified.size()/4)) - ni = 0; - - if (simplified[i*4+0] == simplified[ni*4+0] && - simplified[i*4+2] == simplified[ni*4+2]) - { - // Degenerate segment, remove. - for (int j = i; j < simplified.size()/4-1; ++j) - { - simplified[j*4+0] = simplified[(j+1)*4+0]; - simplified[j*4+1] = simplified[(j+1)*4+1]; - simplified[j*4+2] = simplified[(j+1)*4+2]; - simplified[j*4+3] = simplified[(j+1)*4+3]; - } - simplified.resize(simplified.size()-4); - } - } -} - static int calcAreaOfPolygon2D(const int* verts, const int nverts) { int area = 0; @@ -507,54 +463,155 @@ static int calcAreaOfPolygon2D(const int* verts, const int nverts) return (area+1) / 2; } -inline bool ileft(const int* a, const int* b, const int* c) +// TODO: these are the same as in RecastMesh.cpp, consider using the same. +// Last time I checked the if version got compiled using cmov, which was a lot faster than module (with idiv). +inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; } +inline int next(int i, int n) { return i+1 < n ? i+1 : 0; } + +inline int area2(const int* a, const int* b, const int* c) +{ + return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]); +} + +// Exclusive or: true iff exactly one argument is true. +// The arguments are negated to ensure that they are 0/1 +// values. Then the bitwise Xor operator may apply. +// (This idea is due to Michael Baldwin.) +inline bool xorb(bool x, bool y) +{ + return !x ^ !y; +} + +// Returns true iff c is strictly to the left of the directed +// line through a to b. +inline bool left(const int* a, const int* b, const int* c) +{ + return area2(a, b, c) < 0; +} + +inline bool leftOn(const int* a, const int* b, const int* c) +{ + return area2(a, b, c) <= 0; +} + +inline bool collinear(const int* a, const int* b, const int* c) +{ + return area2(a, b, c) == 0; +} + +// Returns true iff ab properly intersects cd: they share +// a point interior to both segments. The properness of the +// intersection is ensured by using strict leftness. +static bool intersectProp(const int* a, const int* b, const int* c, const int* d) +{ + // Eliminate improper cases. + if (collinear(a,b,c) || collinear(a,b,d) || + collinear(c,d,a) || collinear(c,d,b)) + return false; + + return xorb(left(a,b,c), left(a,b,d)) && xorb(left(c,d,a), left(c,d,b)); +} + +// Returns T iff (a,b,c) are collinear and point c lies +// on the closed segement ab. +static bool between(const int* a, const int* b, const int* c) +{ + if (!collinear(a, b, c)) + return false; + // If ab not vertical, check betweenness on x; else on y. + if (a[0] != b[0]) + return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0])); + else + return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2])); +} + +// Returns true iff segments ab and cd intersect, properly or improperly. +static bool intersect(const int* a, const int* b, const int* c, const int* d) +{ + if (intersectProp(a, b, c, d)) + return true; + else if (between(a, b, c) || between(a, b, d) || + between(c, d, a) || between(c, d, b)) + return true; + else + return false; +} + +static bool vequal(const int* a, const int* b) +{ + return a[0] == b[0] && a[2] == b[2]; +} + +static bool intersectSegCountour(const int* d0, const int* d1, int i, int n, const int* verts) { - return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]) <= 0; + // For each edge (k,k+1) of P + for (int k = 0; k < n; k++) + { + int k1 = next(k, n); + // Skip edges incident to i. + if (i == k || i == k1) + continue; + const int* p0 = &verts[k * 4]; + const int* p1 = &verts[k1 * 4]; + if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1)) + continue; + + if (intersect(d0, d1, p0, p1)) + return true; + } + return false; } -static void getClosestIndices(const int* vertsa, const int nvertsa, - const int* vertsb, const int nvertsb, - int& ia, int& ib) +static bool inCone(int i, int n, const int* verts, const int* pj) { - int closestDist = 0xfffffff; - ia = -1, ib = -1; - for (int i = 0; i < nvertsa; ++i) + const int* pi = &verts[i * 4]; + const int* pi1 = &verts[next(i, n) * 4]; + const int* pin1 = &verts[prev(i, n) * 4]; + + // If P[i] is a convex vertex [ i+1 left or on (i-1,i) ]. + if (leftOn(pin1, pi, pi1)) + return left(pi, pj, pin1) && left(pj, pi, pi1); + // Assume (i-1,i,i+1) not collinear. + // else P[i] is reflex. + return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1)); +} + + +static void removeDegenerateSegments(rcIntArray& simplified) +{ + // Remove adjacent vertices which are equal on xz-plane, + // or else the triangulator will get confused. + int npts = simplified.size()/4; + for (int i = 0; i < npts; ++i) { - const int in = (i+1) % nvertsa; - const int ip = (i+nvertsa-1) % nvertsa; - const int* va = &vertsa[i*4]; - const int* van = &vertsa[in*4]; - const int* vap = &vertsa[ip*4]; + int ni = next(i, npts); - for (int j = 0; j < nvertsb; ++j) + if (vequal(&simplified[i*4], &simplified[ni*4])) { - const int* vb = &vertsb[j*4]; - // vb must be "infront" of va. - if (ileft(vap,va,vb) && ileft(va,van,vb)) + // Degenerate segment, remove. + for (int j = i; j < simplified.size()/4-1; ++j) { - const int dx = vb[0] - va[0]; - const int dz = vb[2] - va[2]; - const int d = dx*dx + dz*dz; - if (d < closestDist) - { - ia = i; - ib = j; - closestDist = d; - } + simplified[j*4+0] = simplified[(j+1)*4+0]; + simplified[j*4+1] = simplified[(j+1)*4+1]; + simplified[j*4+2] = simplified[(j+1)*4+2]; + simplified[j*4+3] = simplified[(j+1)*4+3]; } + simplified.resize(simplified.size()-4); + npts--; } } } + static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib) { const int maxVerts = ca.nverts + cb.nverts + 2; int* verts = (int*)rcAlloc(sizeof(int)*maxVerts*4, RC_ALLOC_PERM); if (!verts) return false; - + int nv = 0; - + // Copy contour A. for (int i = 0; i <= ca.nverts; ++i) { @@ -582,7 +639,7 @@ static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib) rcFree(ca.verts); ca.verts = verts; ca.nverts = nv; - + rcFree(cb.verts); cb.verts = 0; cb.nverts = 0; @@ -590,18 +647,179 @@ static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib) return true; } +struct rcContourHole +{ + rcContour* contour; + int minx, minz, leftmost; +}; + +struct rcContourRegion +{ + rcContour* outline; + rcContourHole* holes; + int nholes; +}; + +struct rcPotentialDiagonal +{ + int vert; + int dist; +}; + +// Finds the lowest leftmost vertex of a contour. +static void findLeftMostVertex(rcContour* contour, int* minx, int* minz, int* leftmost) +{ + *minx = contour->verts[0]; + *minz = contour->verts[2]; + *leftmost = 0; + for (int i = 1; i < contour->nverts; i++) + { + const int x = contour->verts[i*4+0]; + const int z = contour->verts[i*4+2]; + if (x < *minx || (x == *minx && z < *minz)) + { + *minx = x; + *minz = z; + *leftmost = i; + } + } +} + +static int compareHoles(const void* va, const void* vb) +{ + const rcContourHole* a = (const rcContourHole*)va; + const rcContourHole* b = (const rcContourHole*)vb; + if (a->minx == b->minx) + { + if (a->minz < b->minz) + return -1; + if (a->minz > b->minz) + return 1; + } + else + { + if (a->minx < b->minx) + return -1; + if (a->minx > b->minx) + return 1; + } + return 0; +} + + +static int compareDiagDist(const void* va, const void* vb) +{ + const rcPotentialDiagonal* a = (const rcPotentialDiagonal*)va; + const rcPotentialDiagonal* b = (const rcPotentialDiagonal*)vb; + if (a->dist < b->dist) + return -1; + if (a->dist > b->dist) + return 1; + return 0; +} + + +static void mergeRegionHoles(rcContext* ctx, rcContourRegion& region) +{ + // Sort holes from left to right. + for (int i = 0; i < region.nholes; i++) + findLeftMostVertex(region.holes[i].contour, ®ion.holes[i].minx, ®ion.holes[i].minz, ®ion.holes[i].leftmost); + + qsort(region.holes, region.nholes, sizeof(rcContourHole), compareHoles); + + int maxVerts = region.outline->nverts; + for (int i = 0; i < region.nholes; i++) + maxVerts += region.holes[i].contour->nverts; + + rcScopedDelete diags((rcPotentialDiagonal*)rcAlloc(sizeof(rcPotentialDiagonal)*maxVerts, RC_ALLOC_TEMP)); + if (!diags) + { + ctx->log(RC_LOG_WARNING, "mergeRegionHoles: Failed to allocated diags %d.", maxVerts); + return; + } + + rcContour* outline = region.outline; + + // Merge holes into the outline one by one. + for (int i = 0; i < region.nholes; i++) + { + rcContour* hole = region.holes[i].contour; + + int index = -1; + int bestVertex = region.holes[i].leftmost; + for (int iter = 0; iter < hole->nverts; iter++) + { + // Find potential diagonals. + // The 'best' vertex must be in the cone described by 3 cosequtive vertices of the outline. + // ..o j-1 + // | + // | * best + // | + // j o-----o j+1 + // : + int ndiags = 0; + const int* corner = &hole->verts[bestVertex*4]; + for (int j = 0; j < outline->nverts; j++) + { + if (inCone(j, outline->nverts, outline->verts, corner)) + { + int dx = outline->verts[j*4+0] - corner[0]; + int dz = outline->verts[j*4+2] - corner[2]; + diags[ndiags].vert = j; + diags[ndiags].dist = dx*dx + dz*dz; + ndiags++; + } + } + // Sort potential diagonals by distance, we want to make the connection as short as possible. + qsort(diags, ndiags, sizeof(rcPotentialDiagonal), compareDiagDist); + + // Find a diagonal that is not intersecting the outline not the remaining holes. + index = -1; + for (int j = 0; j < ndiags; j++) + { + const int* pt = &outline->verts[diags[j].vert*4]; + bool intersect = intersectSegCountour(pt, corner, diags[i].vert, outline->nverts, outline->verts); + for (int k = i; k < region.nholes && !intersect; k++) + intersect |= intersectSegCountour(pt, corner, -1, region.holes[k].contour->nverts, region.holes[k].contour->verts); + if (!intersect) + { + index = diags[j].vert; + break; + } + } + // If found non-intersecting diagonal, stop looking. + if (index != -1) + break; + // All the potential diagonals for the current vertex were intersecting, try next vertex. + bestVertex = (bestVertex + 1) % hole->nverts; + } + + if (index == -1) + { + ctx->log(RC_LOG_WARNING, "mergeHoles: Failed to find merge points for %p and %p.", region.outline, hole); + continue; + } + if (!mergeContours(*region.outline, *hole, index, bestVertex)) + { + ctx->log(RC_LOG_WARNING, "mergeHoles: Failed to merge contours %p and %p.", region.outline, hole); + continue; + } + } +} + + /// @par /// /// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen /// parameters control how closely the simplified contours will match the raw contours. /// -/// Simplified contours are generated such that the vertices for portals between areas match up. +/// Simplified contours are generated such that the vertices for portals between areas match up. /// (They are considered mandatory vertices.) /// /// Setting @p maxEdgeLength to zero will disabled the edge length feature. -/// +/// /// See the #rcConfig documentation for more information on the configuration parameters. -/// +/// /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, const float maxError, const int maxEdgeLen, @@ -613,7 +831,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, const int h = chf.height; const int borderSize = chf.borderSize; - ctx->startTimer(RC_TIMER_BUILD_CONTOURS); + rcScopedTimer timer(ctx, RC_TIMER_BUILD_CONTOURS); rcVcopy(cset.bmin, chf.bmin); rcVcopy(cset.bmax, chf.bmax); @@ -631,6 +849,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, cset.width = chf.width - chf.borderSize*2; cset.height = chf.height - chf.borderSize*2; cset.borderSize = chf.borderSize; + cset.maxError = maxError; int maxContours = rcMax((int)chf.maxRegions, 8); cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); @@ -638,7 +857,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, return false; cset.nconts = 0; - rcScopedDelete flags = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); + rcScopedDelete flags((unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP)); if (!flags) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' (%d).", chf.spanCount); @@ -704,17 +923,17 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, verts.resize(0); simplified.resize(0); - + ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); walkContour(x, y, i, chf, flags, verts); ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); - + ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); removeDegenerateSegments(simplified); ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); - + // Store region->contour remap info. // Create contour. if (simplified.size()/4 >= 3) @@ -722,7 +941,7 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, if (cset.nconts >= maxContours) { // Allocate more contours. - // This can happen when there are tiny holes in the heightfield. + // This happens when a region has holes. const int oldMax = maxContours; maxContours *= 2; rcContour* newConts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); @@ -735,10 +954,10 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, } rcFree(cset.conts); cset.conts = newConts; - + ctx->log(RC_LOG_WARNING, "rcBuildContours: Expanding max contours from %d to %d.", oldMax, maxContours); } - + rcContour* cont = &cset.conts[cset.nconts++]; cont->nverts = simplified.size()/4; @@ -779,17 +998,6 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, } } -/* cont->cx = cont->cy = cont->cz = 0; - for (int i = 0; i < cont->nverts; ++i) - { - cont->cx += cont->verts[i*4+0]; - cont->cy += cont->verts[i*4+1]; - cont->cz += cont->verts[i*4+2]; - } - cont->cx /= cont->nverts; - cont->cy /= cont->nverts; - cont->cz /= cont->nverts;*/ - cont->reg = reg; cont->area = area; } @@ -797,55 +1005,101 @@ bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, } } - // Check and merge droppings. - // Sometimes the previous algorithms can fail and create several contours - // per area. This pass will try to merge the holes into the main region. - for (int i = 0; i < cset.nconts; ++i) + // Merge holes if needed. + if (cset.nconts > 0) { - rcContour& cont = cset.conts[i]; - // Check if the contour is would backwards. - if (calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0) + // Calculate winding of all polygons. + rcScopedDelete winding((char*)rcAlloc(sizeof(char)*cset.nconts, RC_ALLOC_TEMP)); + if (!winding) + { + ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'hole' (%d).", cset.nconts); + return false; + } + int nholes = 0; + for (int i = 0; i < cset.nconts; ++i) { - // Find another contour which has the same region ID. - int mergeIdx = -1; - for (int j = 0; j < cset.nconts; ++j) + rcContour& cont = cset.conts[i]; + // If the contour is wound backwards, it is a hole. + winding[i] = calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0 ? -1 : 1; + if (winding[i] < 0) + nholes++; + } + + if (nholes > 0) + { + // Collect outline contour and holes contours per region. + // We assume that there is one outline and multiple holes. + const int nregions = chf.maxRegions+1; + rcScopedDelete regions((rcContourRegion*)rcAlloc(sizeof(rcContourRegion)*nregions, RC_ALLOC_TEMP)); + if (!regions) + { + ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'regions' (%d).", nregions); + return false; + } + memset(regions, 0, sizeof(rcContourRegion)*nregions); + + rcScopedDelete holes((rcContourHole*)rcAlloc(sizeof(rcContourHole)*cset.nconts, RC_ALLOC_TEMP)); + if (!holes) { - if (i == j) continue; - if (cset.conts[j].nverts && cset.conts[j].reg == cont.reg) + ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'holes' (%d).", cset.nconts); + return false; + } + memset(holes, 0, sizeof(rcContourHole)*cset.nconts); + + for (int i = 0; i < cset.nconts; ++i) + { + rcContour& cont = cset.conts[i]; + // Positively would contours are outlines, negative holes. + if (winding[i] > 0) { - // Make sure the polygon is correctly oriented. - if (calcAreaOfPolygon2D(cset.conts[j].verts, cset.conts[j].nverts)) - { - mergeIdx = j; - break; - } + if (regions[cont.reg].outline) + ctx->log(RC_LOG_ERROR, "rcBuildContours: Multiple outlines for region %d.", cont.reg); + regions[cont.reg].outline = &cont; + } + else + { + regions[cont.reg].nholes++; + } + } + int index = 0; + for (int i = 0; i < nregions; i++) + { + if (regions[i].nholes > 0) + { + regions[i].holes = &holes[index]; + index += regions[i].nholes; + regions[i].nholes = 0; } } - if (mergeIdx == -1) + for (int i = 0; i < cset.nconts; ++i) { - ctx->log(RC_LOG_WARNING, "rcBuildContours: Could not find merge target for bad contour %d.", i); + rcContour& cont = cset.conts[i]; + rcContourRegion& reg = regions[cont.reg]; + if (winding[i] < 0) + reg.holes[reg.nholes++].contour = &cont; } - else + + // Finally merge each regions holes into the outline. + for (int i = 0; i < nregions; i++) { - rcContour& mcont = cset.conts[mergeIdx]; - // Merge by closest points. - int ia = 0, ib = 0; - getClosestIndices(mcont.verts, mcont.nverts, cont.verts, cont.nverts, ia, ib); - if (ia == -1 || ib == -1) + rcContourRegion& reg = regions[i]; + if (!reg.nholes) continue; + + if (reg.outline) { - ctx->log(RC_LOG_WARNING, "rcBuildContours: Failed to find merge points for %d and %d.", i, mergeIdx); - continue; + mergeRegionHoles(ctx, reg); } - if (!mergeContours(mcont, cont, ia, ib)) + else { - ctx->log(RC_LOG_WARNING, "rcBuildContours: Failed to merge contours %d and %d.", i, mergeIdx); - continue; + // The region does not have an outline. + // This can happen if the contour becaomes selfoverlapping because of + // too aggressive simplification settings. + ctx->log(RC_LOG_ERROR, "rcBuildContours: Bad outline for region %d, contour simplification is likely too aggressive.", i); } } } + } - ctx->stopTimer(RC_TIMER_BUILD_CONTOURS); - return true; } diff --git a/Engine/lib/recast/Recast/Source/RecastFilter.cpp b/Engine/lib/recast/Recast/Source/RecastFilter.cpp index bf985c362c..9d3e63c482 100644 --- a/Engine/lib/recast/Recast/Source/RecastFilter.cpp +++ b/Engine/lib/recast/Recast/Source/RecastFilter.cpp @@ -37,7 +37,7 @@ void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb { rcAssert(ctx); - ctx->startTimer(RC_TIMER_FILTER_LOW_OBSTACLES); + rcScopedTimer timer(ctx, RC_TIMER_FILTER_LOW_OBSTACLES); const int w = solid.width; const int h = solid.height; @@ -67,8 +67,6 @@ void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb } } } - - ctx->stopTimer(RC_TIMER_FILTER_LOW_OBSTACLES); } /// @par @@ -86,7 +84,7 @@ void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walk { rcAssert(ctx); - ctx->startTimer(RC_TIMER_FILTER_BORDER); + rcScopedTimer timer(ctx, RC_TIMER_FILTER_BORDER); const int w = solid.width; const int h = solid.height; @@ -156,20 +154,19 @@ void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walk // The current span is close to a ledge if the drop to any // neighbour span is less than the walkableClimb. if (minh < -walkableClimb) + { s->area = RC_NULL_AREA; - + } // If the difference between all neighbours is too large, // we are at steep slope, mark the span as ledge. - if ((asmax - asmin) > walkableClimb) + else if ((asmax - asmin) > walkableClimb) { s->area = RC_NULL_AREA; } } } } - - ctx->stopTimer(RC_TIMER_FILTER_BORDER); -} +} /// @par /// @@ -181,7 +178,7 @@ void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeight { rcAssert(ctx); - ctx->startTimer(RC_TIMER_FILTER_WALKABLE); + rcScopedTimer timer(ctx, RC_TIMER_FILTER_WALKABLE); const int w = solid.width; const int h = solid.height; @@ -202,6 +199,4 @@ void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeight } } } - - ctx->stopTimer(RC_TIMER_FILTER_WALKABLE); } diff --git a/Engine/lib/recast/Recast/Source/RecastLayers.cpp b/Engine/lib/recast/Recast/Source/RecastLayers.cpp index c6168107a9..22a357effa 100644 --- a/Engine/lib/recast/Recast/Source/RecastLayers.cpp +++ b/Engine/lib/recast/Recast/Source/RecastLayers.cpp @@ -38,7 +38,7 @@ struct rcLayerRegion unsigned char layerId; // Layer ID unsigned char nlayers; // Layer count unsigned char nneis; // Neighbour count - unsigned char base; // Flag indicating if the region is hte base of merged regions. + unsigned char base; // Flag indicating if the region is the base of merged regions. }; @@ -87,12 +87,12 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, { rcAssert(ctx); - ctx->startTimer(RC_TIMER_BUILD_LAYERS); + rcScopedTimer timer(ctx, RC_TIMER_BUILD_LAYERS); const int w = chf.width; const int h = chf.height; - rcScopedDelete srcReg = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); + rcScopedDelete srcReg((unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP)); if (!srcReg) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' (%d).", chf.spanCount); @@ -101,7 +101,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, memset(srcReg,0xff,sizeof(unsigned char)*chf.spanCount); const int nsweeps = chf.width; - rcScopedDelete sweeps = (rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP); + rcScopedDelete sweeps((rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP)); if (!sweeps) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps); @@ -212,7 +212,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, // Allocate and init layer regions. const int nregs = (int)regId; - rcScopedDelete regs = (rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP); + rcScopedDelete regs((rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP)); if (!regs) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs); @@ -258,7 +258,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); const unsigned char rai = srcReg[ai]; - if (rai != 0xff && rai != ri) + if (rai != 0xff && rai != ri && regs[ri].nneis < RC_MAX_NEIS) addUnique(regs[ri].neis, regs[ri].nneis, rai); } } @@ -293,7 +293,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, for (int i = 0; i < nregs; ++i) { rcLayerRegion& root = regs[i]; - // Skip alreadu visited. + // Skip already visited. if (root.layerId != 0xff) continue; @@ -325,7 +325,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, continue; // Skip if the height range would become too large. const int ymin = rcMin(root.ymin, regn.ymin); - const int ymax = rcMin(root.ymax, regn.ymax); + const int ymax = rcMax(root.ymax, regn.ymax); if ((ymax - ymin) >= 255) continue; @@ -368,16 +368,16 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, rcLayerRegion& rj = regs[j]; if (!rj.base) continue; - // Skip if teh regions are not close to each other. + // Skip if the regions are not close to each other. if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight)) continue; // Skip if the height range would become too large. const int ymin = rcMin(ri.ymin, rj.ymin); - const int ymax = rcMin(ri.ymax, rj.ymax); + const int ymax = rcMax(ri.ymax, rj.ymax); if ((ymax - ymin) >= 255) continue; - // Make sure that there is no overlap when mergin 'ri' and 'rj'. + // Make sure that there is no overlap when merging 'ri' and 'rj'. bool overlap = false; // Iterate over all regions which have the same layerId as 'rj' for (int k = 0; k < nregs; ++k) @@ -417,7 +417,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, // Add overlaid layers from 'rj' to 'ri'. for (int k = 0; k < rj.nlayers; ++k) addUnique(ri.layers, ri.nlayers, rj.layers[k]); - // Update heigh bounds. + // Update height bounds. ri.ymin = rcMin(ri.ymin, rj.ymin); ri.ymax = rcMax(ri.ymax, rj.ymax); } @@ -446,10 +446,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, // No layers, return empty. if (layerId == 0) - { - ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; - } // Create layers. rcAssert(lset.layers == 0); @@ -481,10 +478,8 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, for (int i = 0; i < lset.nlayers; ++i) { unsigned char curId = (unsigned char)i; - - // Allocate memory for the current layer. + rcHeightfieldLayer* layer = &lset.layers[i]; - memset(layer, 0, sizeof(rcHeightfieldLayer)); const int gridSize = sizeof(unsigned char)*lw*lh; @@ -528,7 +523,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, layer->cs = chf.cs; layer->ch = chf.ch; - // Adjust the bbox to fit the heighfield. + // Adjust the bbox to fit the heightfield. rcVcopy(layer->bmin, bmin); rcVcopy(layer->bmax, bmax); layer->bmin[1] = bmin[1] + hmin*chf.ch; @@ -542,7 +537,7 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, layer->miny = layer->height; layer->maxy = 0; - // Copy height and area from compact heighfield. + // Copy height and area from compact heightfield. for (int y = 0; y < lh; ++y) { for (int x = 0; x < lw; ++x) @@ -614,7 +609,5 @@ bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, layer->miny = layer->maxy = 0; } - ctx->stopTimer(RC_TIMER_BUILD_LAYERS); - return true; } diff --git a/Engine/lib/recast/Recast/Source/RecastMesh.cpp b/Engine/lib/recast/Recast/Source/RecastMesh.cpp index 13aad2af01..9b6f04e309 100644 --- a/Engine/lib/recast/Recast/Source/RecastMesh.cpp +++ b/Engine/lib/recast/Recast/Source/RecastMesh.cpp @@ -160,6 +160,7 @@ static unsigned short addVertex(unsigned short x, unsigned short y, unsigned sho return (unsigned short)i; } +// Last time I checked the if version got compiled using cmov, which was a lot faster than module (with idiv). inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; } inline int next(int i, int n) { return i+1 < n ? i+1 : 0; } @@ -288,6 +289,53 @@ static bool diagonal(int i, int j, int n, const int* verts, int* indices) return inCone(i, j, n, verts, indices) && diagonalie(i, j, n, verts, indices); } + +static bool diagonalieLoose(int i, int j, int n, const int* verts, int* indices) +{ + const int* d0 = &verts[(indices[i] & 0x0fffffff) * 4]; + const int* d1 = &verts[(indices[j] & 0x0fffffff) * 4]; + + // For each edge (k,k+1) of P + for (int k = 0; k < n; k++) + { + int k1 = next(k, n); + // Skip edges incident to i or j + if (!((k == i) || (k1 == i) || (k == j) || (k1 == j))) + { + const int* p0 = &verts[(indices[k] & 0x0fffffff) * 4]; + const int* p1 = &verts[(indices[k1] & 0x0fffffff) * 4]; + + if (vequal(d0, p0) || vequal(d1, p0) || vequal(d0, p1) || vequal(d1, p1)) + continue; + + if (intersectProp(d0, d1, p0, p1)) + return false; + } + } + return true; +} + +static bool inConeLoose(int i, int j, int n, const int* verts, int* indices) +{ + const int* pi = &verts[(indices[i] & 0x0fffffff) * 4]; + const int* pj = &verts[(indices[j] & 0x0fffffff) * 4]; + const int* pi1 = &verts[(indices[next(i, n)] & 0x0fffffff) * 4]; + const int* pin1 = &verts[(indices[prev(i, n)] & 0x0fffffff) * 4]; + + // If P[i] is a convex vertex [ i+1 left or on (i-1,i) ]. + if (leftOn(pin1, pi, pi1)) + return leftOn(pi, pj, pin1) && leftOn(pj, pi, pi1); + // Assume (i-1,i,i+1) not collinear. + // else P[i] is reflex. + return !(leftOn(pi, pj, pi1) && leftOn(pj, pi, pin1)); +} + +static bool diagonalLoose(int i, int j, int n, const int* verts, int* indices) +{ + return inConeLoose(i, j, n, verts, indices) && diagonalieLoose(i, j, n, verts, indices); +} + + static int triangulate(int n, const int* verts, int* indices, int* tris) { int ntris = 0; @@ -328,14 +376,41 @@ static int triangulate(int n, const int* verts, int* indices, int* tris) if (mini == -1) { - // Should not happen. -/* printf("mini == -1 ntris=%d n=%d\n", ntris, n); + // We might get here because the contour has overlapping segments, like this: + // + // A o-o=====o---o B + // / |C D| \ + // o o o o + // : : : : + // We'll try to recover by loosing up the inCone test a bit so that a diagonal + // like A-B or C-D can be found and we can continue. + minLen = -1; + mini = -1; for (int i = 0; i < n; i++) { - printf("%d ", indices[i] & 0x0fffffff); + int i1 = next(i, n); + int i2 = next(i1, n); + if (diagonalLoose(i, i2, n, verts, indices)) + { + const int* p0 = &verts[(indices[i] & 0x0fffffff) * 4]; + const int* p2 = &verts[(indices[next(i2, n)] & 0x0fffffff) * 4]; + int dx = p2[0] - p0[0]; + int dy = p2[2] - p0[2]; + int len = dx*dx + dy*dy; + + if (minLen < 0 || len < minLen) + { + minLen = len; + mini = i; + } + } + } + if (mini == -1) + { + // The contour is messed up. This sometimes happens + // if the contour simplification is too aggressive. + return -ntris; } - printf("\n");*/ - return -ntris; } int i = mini; @@ -453,8 +528,8 @@ static int getPolyMergeValue(unsigned short* pa, unsigned short* pb, return dx*dx + dy*dy; } -static void mergePolys(unsigned short* pa, unsigned short* pb, int ea, int eb, - unsigned short* tmp, const int nvp) +static void mergePolyVerts(unsigned short* pa, unsigned short* pb, int ea, int eb, + unsigned short* tmp, const int nvp) { const int na = countPolyVerts(pa, nvp); const int nb = countPolyVerts(pb, nvp); @@ -526,7 +601,7 @@ static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned sho // Find edges which share the removed vertex. const int maxEdges = numTouchedVerts*2; int nedges = 0; - rcScopedDelete edges = (int*)rcAlloc(sizeof(int)*maxEdges*3, RC_ALLOC_TEMP); + rcScopedDelete edges((int*)rcAlloc(sizeof(int)*maxEdges*3, RC_ALLOC_TEMP)); if (!edges) { ctx->log(RC_LOG_WARNING, "canRemoveVertex: Out of memory 'edges' (%d).", maxEdges*3); @@ -606,7 +681,7 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short } int nedges = 0; - rcScopedDelete edges = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp*4, RC_ALLOC_TEMP); + rcScopedDelete edges((int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp*4, RC_ALLOC_TEMP)); if (!edges) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'edges' (%d).", numRemovedVerts*nvp*4); @@ -614,15 +689,15 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short } int nhole = 0; - rcScopedDelete hole = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); + rcScopedDelete hole((int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP)); if (!hole) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hole' (%d).", numRemovedVerts*nvp); return false; } - + int nhreg = 0; - rcScopedDelete hreg = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); + rcScopedDelete hreg((int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP)); if (!hreg) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hreg' (%d).", numRemovedVerts*nvp); @@ -630,7 +705,7 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short } int nharea = 0; - rcScopedDelete harea = (int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP); + rcScopedDelete harea((int*)rcAlloc(sizeof(int)*numRemovedVerts*nvp, RC_ALLOC_TEMP)); if (!harea) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'harea' (%d).", numRemovedVerts*nvp); @@ -661,7 +736,8 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short } // Remove the polygon. unsigned short* p2 = &mesh.polys[(mesh.npolys-1)*nvp*2]; - memcpy(p,p2,sizeof(unsigned short)*nvp); + if (p != p2) + memcpy(p,p2,sizeof(unsigned short)*nvp); memset(p+nvp,0xff,sizeof(unsigned short)*nvp); mesh.regs[i] = mesh.regs[mesh.npolys-1]; mesh.areas[i] = mesh.areas[mesh.npolys-1]; @@ -671,7 +747,7 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short } // Remove vertex. - for (int i = (int)rem; i < mesh.nverts; ++i) + for (int i = (int)rem; i < mesh.nverts - 1; ++i) { mesh.verts[i*3+0] = mesh.verts[(i+1)*3+0]; mesh.verts[i*3+1] = mesh.verts[(i+1)*3+1]; @@ -746,22 +822,22 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short break; } - rcScopedDelete tris = (int*)rcAlloc(sizeof(int)*nhole*3, RC_ALLOC_TEMP); + rcScopedDelete tris((int*)rcAlloc(sizeof(int)*nhole*3, RC_ALLOC_TEMP)); if (!tris) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tris' (%d).", nhole*3); return false; } - rcScopedDelete tverts = (int*)rcAlloc(sizeof(int)*nhole*4, RC_ALLOC_TEMP); + rcScopedDelete tverts((int*)rcAlloc(sizeof(int)*nhole*4, RC_ALLOC_TEMP)); if (!tverts) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' (%d).", nhole*4); return false; } - rcScopedDelete thole = (int*)rcAlloc(sizeof(int)*nhole, RC_ALLOC_TEMP); - if (!tverts) + rcScopedDelete thole((int*)rcAlloc(sizeof(int)*nhole, RC_ALLOC_TEMP)); + if (!thole) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'thole' (%d).", nhole); return false; @@ -787,20 +863,20 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short } // Merge the hole triangles back to polygons. - rcScopedDelete polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*(ntris+1)*nvp, RC_ALLOC_TEMP); + rcScopedDelete polys((unsigned short*)rcAlloc(sizeof(unsigned short)*(ntris+1)*nvp, RC_ALLOC_TEMP)); if (!polys) { ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'polys' (%d).", (ntris+1)*nvp); return false; } - rcScopedDelete pregs = (unsigned short*)rcAlloc(sizeof(unsigned short)*ntris, RC_ALLOC_TEMP); + rcScopedDelete pregs((unsigned short*)rcAlloc(sizeof(unsigned short)*ntris, RC_ALLOC_TEMP)); if (!pregs) { ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pregs' (%d).", ntris); return false; } - rcScopedDelete pareas = (unsigned char*)rcAlloc(sizeof(unsigned char)*ntris, RC_ALLOC_TEMP); - if (!pregs) + rcScopedDelete pareas((unsigned char*)rcAlloc(sizeof(unsigned char)*ntris, RC_ALLOC_TEMP)); + if (!pareas) { ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pareas' (%d).", ntris); return false; @@ -819,7 +895,14 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short polys[npolys*nvp+0] = (unsigned short)hole[t[0]]; polys[npolys*nvp+1] = (unsigned short)hole[t[1]]; polys[npolys*nvp+2] = (unsigned short)hole[t[2]]; - pregs[npolys] = (unsigned short)hreg[t[0]]; + + // If this polygon covers multiple region types then + // mark it as such + if (hreg[t[0]] != hreg[t[1]] || hreg[t[1]] != hreg[t[2]]) + pregs[npolys] = RC_MULTIPLE_REGS; + else + pregs[npolys] = (unsigned short)hreg[t[0]]; + pareas[npolys] = (unsigned char)harea[t[0]]; npolys++; } @@ -860,8 +943,13 @@ static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short // Found best, merge. unsigned short* pa = &polys[bestPa*nvp]; unsigned short* pb = &polys[bestPb*nvp]; - mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); - memcpy(pb, &polys[(npolys-1)*nvp], sizeof(unsigned short)*nvp); + mergePolyVerts(pa, pb, bestEa, bestEb, tmpPoly, nvp); + if (pregs[bestPa] != pregs[bestPb]) + pregs[bestPa] = RC_MULTIPLE_REGS; + + unsigned short* last = &polys[(npolys-1)*nvp]; + if (pb != last) + memcpy(pb, last, sizeof(unsigned short)*nvp); pregs[bestPb] = pregs[npolys-1]; pareas[bestPb] = pareas[npolys-1]; npolys--; @@ -905,13 +993,14 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe { rcAssert(ctx); - ctx->startTimer(RC_TIMER_BUILD_POLYMESH); + rcScopedTimer timer(ctx, RC_TIMER_BUILD_POLYMESH); rcVcopy(mesh.bmin, cset.bmin); rcVcopy(mesh.bmax, cset.bmax); mesh.cs = cset.cs; mesh.ch = cset.ch; mesh.borderSize = cset.borderSize; + mesh.maxEdgeError = cset.maxError; int maxVertices = 0; int maxTris = 0; @@ -931,7 +1020,7 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe return false; } - rcScopedDelete vflags = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxVertices, RC_ALLOC_TEMP); + rcScopedDelete vflags((unsigned char*)rcAlloc(sizeof(unsigned char)*maxVertices, RC_ALLOC_TEMP)); if (!vflags) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'vflags' (%d).", maxVertices); @@ -974,7 +1063,7 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe memset(mesh.regs, 0, sizeof(unsigned short)*maxTris); memset(mesh.areas, 0, sizeof(unsigned char)*maxTris); - rcScopedDelete nextVert = (int*)rcAlloc(sizeof(int)*maxVertices, RC_ALLOC_TEMP); + rcScopedDelete nextVert((int*)rcAlloc(sizeof(int)*maxVertices, RC_ALLOC_TEMP)); if (!nextVert) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'nextVert' (%d).", maxVertices); @@ -982,7 +1071,7 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe } memset(nextVert, 0, sizeof(int)*maxVertices); - rcScopedDelete firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); + rcScopedDelete firstVert((int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP)); if (!firstVert) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT); @@ -991,19 +1080,19 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) firstVert[i] = -1; - rcScopedDelete indices = (int*)rcAlloc(sizeof(int)*maxVertsPerCont, RC_ALLOC_TEMP); + rcScopedDelete indices((int*)rcAlloc(sizeof(int)*maxVertsPerCont, RC_ALLOC_TEMP)); if (!indices) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'indices' (%d).", maxVertsPerCont); return false; } - rcScopedDelete tris = (int*)rcAlloc(sizeof(int)*maxVertsPerCont*3, RC_ALLOC_TEMP); + rcScopedDelete tris((int*)rcAlloc(sizeof(int)*maxVertsPerCont*3, RC_ALLOC_TEMP)); if (!tris) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'tris' (%d).", maxVertsPerCont*3); return false; } - rcScopedDelete polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*(maxVertsPerCont+1)*nvp, RC_ALLOC_TEMP); + rcScopedDelete polys((unsigned short*)rcAlloc(sizeof(unsigned short)*(maxVertsPerCont+1)*nvp, RC_ALLOC_TEMP)); if (!polys) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'polys' (%d).", maxVertsPerCont*nvp); @@ -1104,8 +1193,10 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe // Found best, merge. unsigned short* pa = &polys[bestPa*nvp]; unsigned short* pb = &polys[bestPb*nvp]; - mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); - memcpy(pb, &polys[(npolys-1)*nvp], sizeof(unsigned short)*nvp); + mergePolyVerts(pa, pb, bestEa, bestEb, tmpPoly, nvp); + unsigned short* lastPoly = &polys[(npolys-1)*nvp]; + if (pb != lastPoly) + memcpy(pb, lastPoly, sizeof(unsigned short)*nvp); npolys--; } else @@ -1213,8 +1304,6 @@ bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMe ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff); } - ctx->stopTimer(RC_TIMER_BUILD_POLYMESH); - return true; } @@ -1226,7 +1315,7 @@ bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, r if (!nmeshes || !meshes) return true; - ctx->startTimer(RC_TIMER_MERGE_POLYMESH); + rcScopedTimer timer(ctx, RC_TIMER_MERGE_POLYMESH); mesh.nvp = meshes[0]->nvp; mesh.cs = meshes[0]->cs; @@ -1287,7 +1376,7 @@ bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, r } memset(mesh.flags, 0, sizeof(unsigned short)*maxPolys); - rcScopedDelete nextVert = (int*)rcAlloc(sizeof(int)*maxVerts, RC_ALLOC_TEMP); + rcScopedDelete nextVert((int*)rcAlloc(sizeof(int)*maxVerts, RC_ALLOC_TEMP)); if (!nextVert) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' (%d).", maxVerts); @@ -1295,7 +1384,7 @@ bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, r } memset(nextVert, 0, sizeof(int)*maxVerts); - rcScopedDelete firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); + rcScopedDelete firstVert((int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP)); if (!firstVert) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT); @@ -1304,7 +1393,7 @@ bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, r for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) firstVert[i] = -1; - rcScopedDelete vremap = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerMesh, RC_ALLOC_PERM); + rcScopedDelete vremap((unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerMesh, RC_ALLOC_PERM)); if (!vremap) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' (%d).", maxVertsPerMesh); @@ -1319,6 +1408,12 @@ bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, r const unsigned short ox = (unsigned short)floorf((pmesh->bmin[0]-mesh.bmin[0])/mesh.cs+0.5f); const unsigned short oz = (unsigned short)floorf((pmesh->bmin[2]-mesh.bmin[2])/mesh.cs+0.5f); + bool isMinX = (ox == 0); + bool isMinZ = (oz == 0); + bool isMaxX = ((unsigned short)floorf((mesh.bmax[0] - pmesh->bmax[0]) / mesh.cs + 0.5f)) == 0; + bool isMaxZ = ((unsigned short)floorf((mesh.bmax[2] - pmesh->bmax[2]) / mesh.cs + 0.5f)) == 0; + bool isOnBorder = (isMinX || isMinZ || isMaxX || isMaxZ); + for (int j = 0; j < pmesh->nverts; ++j) { unsigned short* v = &pmesh->verts[j*3]; @@ -1339,6 +1434,36 @@ bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, r if (src[k] == RC_MESH_NULL_IDX) break; tgt[k] = vremap[src[k]]; } + + if (isOnBorder) + { + for (int k = mesh.nvp; k < mesh.nvp * 2; ++k) + { + if (src[k] & 0x8000 && src[k] != 0xffff) + { + unsigned short dir = src[k] & 0xf; + switch (dir) + { + case 0: // Portal x- + if (isMinX) + tgt[k] = src[k]; + break; + case 1: // Portal z+ + if (isMaxZ) + tgt[k] = src[k]; + break; + case 2: // Portal x+ + if (isMaxX) + tgt[k] = src[k]; + break; + case 3: // Portal z- + if (isMinZ) + tgt[k] = src[k]; + break; + } + } + } + } } } @@ -1358,8 +1483,6 @@ bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, r ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff); } - ctx->stopTimer(RC_TIMER_MERGE_POLYMESH); - return true; } @@ -1383,6 +1506,7 @@ bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst) dst.cs = src.cs; dst.ch = src.ch; dst.borderSize = src.borderSize; + dst.maxEdgeError = src.maxEdgeError; dst.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*src.nverts*3, RC_ALLOC_PERM); if (!dst.verts) @@ -1422,7 +1546,7 @@ bool rcCopyPolyMesh(rcContext* ctx, const rcPolyMesh& src, rcPolyMesh& dst) ctx->log(RC_LOG_ERROR, "rcCopyPolyMesh: Out of memory 'dst.flags' (%d).", src.npolys); return false; } - memcpy(dst.flags, src.flags, sizeof(unsigned char)*src.npolys); + memcpy(dst.flags, src.flags, sizeof(unsigned short)*src.npolys); return true; } diff --git a/Engine/lib/recast/Recast/Source/RecastMeshDetail.cpp b/Engine/lib/recast/Recast/Source/RecastMeshDetail.cpp index b387275465..f1270cf202 100644 --- a/Engine/lib/recast/Recast/Source/RecastMeshDetail.cpp +++ b/Engine/lib/recast/Recast/Source/RecastMeshDetail.cpp @@ -56,7 +56,7 @@ inline float vdist2(const float* p, const float* q) } inline float vcross2(const float* p1, const float* p2, const float* p3) -{ +{ const float u1 = p2[0] - p1[0]; const float v1 = p2[2] - p1[2]; const float u2 = p3[0] - p1[0]; @@ -68,21 +68,27 @@ static bool circumCircle(const float* p1, const float* p2, const float* p3, float* c, float& r) { static const float EPS = 1e-6f; + // Calculate the circle relative to p1, to avoid some precision issues. + const float v1[3] = {0,0,0}; + float v2[3], v3[3]; + rcVsub(v2, p2,p1); + rcVsub(v3, p3,p1); - const float cp = vcross2(p1, p2, p3); + const float cp = vcross2(v1, v2, v3); if (fabsf(cp) > EPS) { - const float p1Sq = vdot2(p1,p1); - const float p2Sq = vdot2(p2,p2); - const float p3Sq = vdot2(p3,p3); - c[0] = (p1Sq*(p2[2]-p3[2]) + p2Sq*(p3[2]-p1[2]) + p3Sq*(p1[2]-p2[2])) / (2*cp); - c[2] = (p1Sq*(p3[0]-p2[0]) + p2Sq*(p1[0]-p3[0]) + p3Sq*(p2[0]-p1[0])) / (2*cp); - r = vdist2(c, p1); + const float v1Sq = vdot2(v1,v1); + const float v2Sq = vdot2(v2,v2); + const float v3Sq = vdot2(v3,v3); + c[0] = (v1Sq*(v2[2]-v3[2]) + v2Sq*(v3[2]-v1[2]) + v3Sq*(v1[2]-v2[2])) / (2*cp); + c[1] = 0; + c[2] = (v1Sq*(v3[0]-v2[0]) + v2Sq*(v1[0]-v3[0]) + v3Sq*(v2[0]-v1[0])) / (2*cp); + r = vdist2(c, v1); + rcVadd(c, c, p1); return true; } - - c[0] = p1[0]; - c[2] = p1[2]; + + rcVcopy(c, p1); r = 0; return false; } @@ -93,7 +99,7 @@ static float distPtTri(const float* p, const float* a, const float* b, const flo rcVsub(v0, c,a); rcVsub(v1, b,a); rcVsub(v2, p,a); - + const float dot00 = vdot2(v0, v0); const float dot01 = vdot2(v0, v1); const float dot02 = vdot2(v0, v2); @@ -178,7 +184,7 @@ static float distToTriMesh(const float* p, const float* verts, const int /*nvert static float distToPoly(int nvert, const float* verts, const float* p) { - + float dmin = FLT_MAX; int i, j, c = 0; for (i = 0, j = nvert-1; i < nvert; j = i++) @@ -196,42 +202,79 @@ static float distToPoly(int nvert, const float* verts, const float* p) static unsigned short getHeight(const float fx, const float fy, const float fz, const float /*cs*/, const float ics, const float ch, - const rcHeightPatch& hp) + const int radius, const rcHeightPatch& hp) { int ix = (int)floorf(fx*ics + 0.01f); int iz = (int)floorf(fz*ics + 0.01f); - ix = rcClamp(ix-hp.xmin, 0, hp.width); - iz = rcClamp(iz-hp.ymin, 0, hp.height); + ix = rcClamp(ix-hp.xmin, 0, hp.width - 1); + iz = rcClamp(iz-hp.ymin, 0, hp.height - 1); unsigned short h = hp.data[ix+iz*hp.width]; if (h == RC_UNSET_HEIGHT) { // Special case when data might be bad. - // Find nearest neighbour pixel which has valid height. - const int off[8*2] = { -1,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1}; + // Walk adjacent cells in a spiral up to 'radius', and look + // for a pixel which has a valid height. + int x = 1, z = 0, dx = 1, dz = 0; + int maxSize = radius * 2 + 1; + int maxIter = maxSize * maxSize - 1; + + int nextRingIterStart = 8; + int nextRingIters = 16; + float dmin = FLT_MAX; - for (int i = 0; i < 8; ++i) + for (int i = 0; i < maxIter; i++) { - const int nx = ix+off[i*2+0]; - const int nz = iz+off[i*2+1]; - if (nx < 0 || nz < 0 || nx >= hp.width || nz >= hp.height) continue; - const unsigned short nh = hp.data[nx+nz*hp.width]; - if (nh == RC_UNSET_HEIGHT) continue; + const int nx = ix + x; + const int nz = iz + z; + + if (nx >= 0 && nz >= 0 && nx < hp.width && nz < hp.height) + { + const unsigned short nh = hp.data[nx + nz*hp.width]; + if (nh != RC_UNSET_HEIGHT) + { + const float d = fabsf(nh*ch - fy); + if (d < dmin) + { + h = nh; + dmin = d; + } + } + } - const float d = fabsf(nh*ch - fy); - if (d < dmin) + // We are searching in a grid which looks approximately like this: + // __________ + // |2 ______ 2| + // | |1 __ 1| | + // | | |__| | | + // | |______| | + // |__________| + // We want to find the best height as close to the center cell as possible. This means that + // if we find a height in one of the neighbor cells to the center, we don't want to + // expand further out than the 8 neighbors - we want to limit our search to the closest + // of these "rings", but the best height in the ring. + // For example, the center is just 1 cell. We checked that at the entrance to the function. + // The next "ring" contains 8 cells (marked 1 above). Those are all the neighbors to the center cell. + // The next one again contains 16 cells (marked 2). In general each ring has 8 additional cells, which + // can be thought of as adding 2 cells around the "center" of each side when we expand the ring. + // Here we detect if we are about to enter the next ring, and if we are and we have found + // a height, we abort the search. + if (i + 1 == nextRingIterStart) { - h = nh; - dmin = d; + if (h != RC_UNSET_HEIGHT) + break; + + nextRingIterStart += nextRingIters; + nextRingIters += 8; } - -/* const float dx = (nx+0.5f)*cs - fx; - const float dz = (nz+0.5f)*cs - fz; - const float d = dx*dx+dz*dz; - if (d < dmin) + + if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) { - h = nh; - dmin = d; - } */ + int tmp = dx; + dx = -dz; + dz = tmp; + } + x += dx; + z += dz; } } return h; @@ -240,8 +283,8 @@ static unsigned short getHeight(const float fx, const float fy, const float fz, enum EdgeValues { - UNDEF = -1, - HULL = -2, + EV_UNDEF = -1, + EV_HULL = -2, }; static int findEdge(const int* edges, int nedges, int s, int t) @@ -252,7 +295,7 @@ static int findEdge(const int* edges, int nedges, int s, int t) if ((e[0] == s && e[1] == t) || (e[0] == t && e[1] == s)) return i; } - return UNDEF; + return EV_UNDEF; } static int addEdge(rcContext* ctx, int* edges, int& nedges, const int maxEdges, int s, int t, int l, int r) @@ -260,12 +303,12 @@ static int addEdge(rcContext* ctx, int* edges, int& nedges, const int maxEdges, if (nedges >= maxEdges) { ctx->log(RC_LOG_ERROR, "addEdge: Too many edges (%d/%d).", nedges, maxEdges); - return UNDEF; + return EV_UNDEF; } - // Add edge if not already in the triangulation. + // Add edge if not already in the triangulation. int e = findEdge(edges, nedges, s, t); - if (e == UNDEF) + if (e == EV_UNDEF) { int* edge = &edges[nedges*4]; edge[0] = s; @@ -276,17 +319,17 @@ static int addEdge(rcContext* ctx, int* edges, int& nedges, const int maxEdges, } else { - return UNDEF; + return EV_UNDEF; } } static void updateLeftFace(int* e, int s, int t, int f) { - if (e[0] == s && e[1] == t && e[2] == UNDEF) + if (e[0] == s && e[1] == t && e[2] == EV_UNDEF) e[2] = f; - else if (e[1] == s && e[0] == t && e[3] == UNDEF) + else if (e[1] == s && e[0] == t && e[3] == EV_UNDEF) e[3] = f; -} +} static int overlapSegSeg2d(const float* a, const float* b, const float* c, const float* d) { @@ -298,7 +341,7 @@ static int overlapSegSeg2d(const float* a, const float* b, const float* c, const float a4 = a3 + a2 - a1; if (a3 * a4 < 0.0f) return 1; - } + } return 0; } @@ -320,28 +363,28 @@ static bool overlapEdges(const float* pts, const int* edges, int nedges, int s1, static void completeFacet(rcContext* ctx, const float* pts, int npts, int* edges, int& nedges, const int maxEdges, int& nfaces, int e) { static const float EPS = 1e-5f; - + int* edge = &edges[e*4]; // Cache s and t. int s,t; - if (edge[2] == UNDEF) + if (edge[2] == EV_UNDEF) { s = edge[0]; t = edge[1]; } - else if (edge[3] == UNDEF) + else if (edge[3] == EV_UNDEF) { s = edge[1]; t = edge[0]; } else { - // Edge already completed. + // Edge already completed. return; } - // Find best point on left of edge. + // Find best point on left of edge. int pt = npts; float c[3] = {0,0,0}; float r = -1; @@ -385,23 +428,23 @@ static void completeFacet(rcContext* ctx, const float* pts, int npts, int* edges } } - // Add new triangle or update edge info if s-t is on hull. + // Add new triangle or update edge info if s-t is on hull. if (pt < npts) { - // Update face information of edge being completed. + // Update face information of edge being completed. updateLeftFace(&edges[e*4], s, t, nfaces); - // Add new edge or update face info of old edge. + // Add new edge or update face info of old edge. e = findEdge(edges, nedges, pt, s); - if (e == UNDEF) - addEdge(ctx, edges, nedges, maxEdges, pt, s, nfaces, UNDEF); + if (e == EV_UNDEF) + addEdge(ctx, edges, nedges, maxEdges, pt, s, nfaces, EV_UNDEF); else updateLeftFace(&edges[e*4], pt, s, nfaces); - // Add new edge or update face info of old edge. + // Add new edge or update face info of old edge. e = findEdge(edges, nedges, t, pt); - if (e == UNDEF) - addEdge(ctx, edges, nedges, maxEdges, t, pt, nfaces, UNDEF); + if (e == EV_UNDEF) + addEdge(ctx, edges, nedges, maxEdges, t, pt, nfaces, EV_UNDEF); else updateLeftFace(&edges[e*4], t, pt, nfaces); @@ -409,7 +452,7 @@ static void completeFacet(rcContext* ctx, const float* pts, int npts, int* edges } else { - updateLeftFace(&edges[e*4], s, t, HULL); + updateLeftFace(&edges[e*4], s, t, EV_HULL); } } @@ -423,18 +466,18 @@ static void delaunayHull(rcContext* ctx, const int npts, const float* pts, edges.resize(maxEdges*4); for (int i = 0, j = nhull-1; i < nhull; j=i++) - addEdge(ctx, &edges[0], nedges, maxEdges, hull[j],hull[i], HULL, UNDEF); + addEdge(ctx, &edges[0], nedges, maxEdges, hull[j],hull[i], EV_HULL, EV_UNDEF); int currentEdge = 0; while (currentEdge < nedges) { - if (edges[currentEdge*4+2] == UNDEF) + if (edges[currentEdge*4+2] == EV_UNDEF) completeFacet(ctx, pts, npts, &edges[0], nedges, maxEdges, nfaces, currentEdge); - if (edges[currentEdge*4+3] == UNDEF) + if (edges[currentEdge*4+3] == EV_UNDEF) completeFacet(ctx, pts, npts, &edges[0], nedges, maxEdges, nfaces, currentEdge); currentEdge++; } - + // Create tris tris.resize(nfaces*4); for (int i = 0; i < nfaces*4; ++i) @@ -489,6 +532,97 @@ static void delaunayHull(rcContext* ctx, const int npts, const float* pts, } } +// Calculate minimum extend of the polygon. +static float polyMinExtent(const float* verts, const int nverts) +{ + float minDist = FLT_MAX; + for (int i = 0; i < nverts; i++) + { + const int ni = (i+1) % nverts; + const float* p1 = &verts[i*3]; + const float* p2 = &verts[ni*3]; + float maxEdgeDist = 0; + for (int j = 0; j < nverts; j++) + { + if (j == i || j == ni) continue; + float d = distancePtSeg2d(&verts[j*3], p1,p2); + maxEdgeDist = rcMax(maxEdgeDist, d); + } + minDist = rcMin(minDist, maxEdgeDist); + } + return rcSqrt(minDist); +} + +// Last time I checked the if version got compiled using cmov, which was a lot faster than module (with idiv). +inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; } +inline int next(int i, int n) { return i+1 < n ? i+1 : 0; } + +static void triangulateHull(const int /*nverts*/, const float* verts, const int nhull, const int* hull, rcIntArray& tris) +{ + int start = 0, left = 1, right = nhull-1; + + // Start from an ear with shortest perimeter. + // This tends to favor well formed triangles as starting point. + float dmin = 0; + for (int i = 0; i < nhull; i++) + { + int pi = prev(i, nhull); + int ni = next(i, nhull); + const float* pv = &verts[hull[pi]*3]; + const float* cv = &verts[hull[i]*3]; + const float* nv = &verts[hull[ni]*3]; + const float d = vdist2(pv,cv) + vdist2(cv,nv) + vdist2(nv,pv); + if (d < dmin) + { + start = i; + left = ni; + right = pi; + dmin = d; + } + } + + // Add first triangle + tris.push(hull[start]); + tris.push(hull[left]); + tris.push(hull[right]); + tris.push(0); + + // Triangulate the polygon by moving left or right, + // depending on which triangle has shorter perimeter. + // This heuristic was chose emprically, since it seems + // handle tesselated straight edges well. + while (next(left, nhull) != right) + { + // Check to see if se should advance left or right. + int nleft = next(left, nhull); + int nright = prev(right, nhull); + + const float* cvleft = &verts[hull[left]*3]; + const float* nvleft = &verts[hull[nleft]*3]; + const float* cvright = &verts[hull[right]*3]; + const float* nvright = &verts[hull[nright]*3]; + const float dleft = vdist2(cvleft, nvleft) + vdist2(nvleft, cvright); + const float dright = vdist2(cvright, nvright) + vdist2(cvleft, nvright); + + if (dleft < dright) + { + tris.push(hull[left]); + tris.push(hull[nleft]); + tris.push(hull[right]); + tris.push(0); + left = nleft; + } + else + { + tris.push(hull[left]); + tris.push(hull[nright]); + tris.push(hull[right]); + tris.push(0); + right = nright; + } + } +} + inline float getJitterX(const int i) { @@ -502,9 +636,9 @@ inline float getJitterY(const int i) static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, const float sampleDist, const float sampleMaxError, - const rcCompactHeightfield& chf, const rcHeightPatch& hp, - float* verts, int& nverts, rcIntArray& tris, - rcIntArray& edges, rcIntArray& samples) + const int heightSearchRadius, const rcCompactHeightfield& chf, + const rcHeightPatch& hp, float* verts, int& nverts, + rcIntArray& tris, rcIntArray& edges, rcIntArray& samples) { static const int MAX_VERTS = 127; static const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts). @@ -512,16 +646,22 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, float edge[(MAX_VERTS_PER_EDGE+1)*3]; int hull[MAX_VERTS]; int nhull = 0; - + nverts = 0; - + for (int i = 0; i < nin; ++i) rcVcopy(&verts[i*3], &in[i*3]); nverts = nin; + edges.resize(0); + tris.resize(0); + const float cs = chf.cs; const float ics = 1.0f/cs; + // Calculate minimum extents of the polygon based on input data. + float minExtent = polyMinExtent(verts, nverts); + // Tessellate outlines. // This is done in separate pass in order to ensure // seamless height values across the ply boundaries. @@ -567,7 +707,7 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, pos[0] = vj[0] + dx*u; pos[1] = vj[1] + dy*u; pos[2] = vj[2] + dz*u; - pos[1] = getHeight(pos[0],pos[1],pos[2], cs, ics, chf.ch, hp)*chf.ch; + pos[1] = getHeight(pos[0],pos[1],pos[2], cs, ics, chf.ch, heightSearchRadius, hp)*chf.ch; } // Simplify samples. int idx[MAX_VERTS_PER_EDGE] = {0,nn}; @@ -628,27 +768,26 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, } } - + // If the polygon minimum extent is small (sliver or small triangle), do not try to add internal points. + if (minExtent < sampleDist*2) + { + triangulateHull(nverts, verts, nhull, hull, tris); + return true; + } + // Tessellate the base mesh. - edges.resize(0); - tris.resize(0); - - delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); + // We're using the triangulateHull instead of delaunayHull as it tends to + // create a bit better triangulation for long thing triangles when there + // are no internal points. + triangulateHull(nverts, verts, nhull, hull, tris); if (tris.size() == 0) { // Could not triangulate the poly, make sure there is some valid data there. - ctx->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon, adding default data."); - for (int i = 2; i < nverts; ++i) - { - tris.push(0); - tris.push(i-1); - tris.push(i); - tris.push(0); - } + ctx->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon (%d verts).", nverts); return true; } - + if (sampleDist > 0) { // Create sample locations in a grid. @@ -676,12 +815,12 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, // Make sure the samples are not too close to the edges. if (distToPoly(nin,in,pt) > -sampleDist/2) continue; samples.push(x); - samples.push(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, hp)); + samples.push(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, heightSearchRadius, hp)); samples.push(z); samples.push(0); // Not added } } - + // Add the samples starting from the one that has the most // error. The procedure stops when all samples are added // or when the max error is within treshold. @@ -690,7 +829,7 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, { if (nverts >= MAX_VERTS) break; - + // Find sample with most error. float bestpt[3] = {0,0,0}; float bestd = 0; @@ -728,45 +867,38 @@ static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, edges.resize(0); tris.resize(0); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); - } + } } - + const int ntris = tris.size()/4; if (ntris > MAX_TRIS) { tris.resize(MAX_TRIS*4); ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from %d to max %d.", ntris, MAX_TRIS); } - + return true; } -static void getHeightData(const rcCompactHeightfield& chf, - const unsigned short* poly, const int npoly, - const unsigned short* verts, const int bs, - rcHeightPatch& hp, rcIntArray& stack) +static void seedArrayWithPolyCenter(rcContext* ctx, const rcCompactHeightfield& chf, + const unsigned short* poly, const int npoly, + const unsigned short* verts, const int bs, + rcHeightPatch& hp, rcIntArray& array) { - // Floodfill the heightfield to get 2D height data, - // starting at vertex locations as seeds. - // Note: Reads to the compact heightfield are offset by border size (bs) // since border size offset is already removed from the polymesh vertices. - memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); - - stack.resize(0); - static const int offset[9*2] = { 0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0, }; - // Use poly vertices as seed points for the flood fill. - for (int j = 0; j < npoly; ++j) + // Find cell closest to a poly vertex + int startCellX = 0, startCellY = 0, startSpanIndex = -1; + int dmin = RC_UNSET_HEIGHT; + for (int j = 0; j < npoly && dmin > 0; ++j) { - int cx = 0, cz = 0, ci =-1; - int dmin = RC_UNSET_HEIGHT; - for (int k = 0; k < 9; ++k) + for (int k = 0; k < 9 && dmin > 0; ++k) { const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0]; const int ay = (int)verts[poly[j]*3+1]; @@ -776,118 +908,210 @@ static void getHeightData(const rcCompactHeightfield& chf, continue; const rcCompactCell& c = chf.cells[(ax+bs)+(az+bs)*chf.width]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni && dmin > 0; ++i) { const rcCompactSpan& s = chf.spans[i]; int d = rcAbs(ay - (int)s.y); if (d < dmin) { - cx = ax; - cz = az; - ci = i; + startCellX = ax; + startCellY = az; + startSpanIndex = i; dmin = d; } } } - if (ci != -1) - { - stack.push(cx); - stack.push(cz); - stack.push(ci); - } } - // Find center of the polygon using flood fill. - int pcx = 0, pcz = 0; + rcAssert(startSpanIndex != -1); + // Find center of the polygon + int pcx = 0, pcy = 0; for (int j = 0; j < npoly; ++j) { pcx += (int)verts[poly[j]*3+0]; - pcz += (int)verts[poly[j]*3+2]; + pcy += (int)verts[poly[j]*3+2]; } pcx /= npoly; - pcz /= npoly; - - for (int i = 0; i < stack.size(); i += 3) - { - int cx = stack[i+0]; - int cy = stack[i+1]; - int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; - hp.data[idx] = 1; - } + pcy /= npoly; - while (stack.size() > 0) + // Use seeds array as a stack for DFS + array.resize(0); + array.push(startCellX); + array.push(startCellY); + array.push(startSpanIndex); + + int dirs[] = { 0, 1, 2, 3 }; + memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height); + // DFS to move to the center. Note that we need a DFS here and can not just move + // directly towards the center without recording intermediate nodes, even though the polygons + // are convex. In very rare we can get stuck due to contour simplification if we do not + // record nodes. + int cx = -1, cy = -1, ci = -1; + while (true) { - int ci = stack.pop(); - int cy = stack.pop(); - int cx = stack.pop(); - - // Check if close to center of the polygon. - if (rcAbs(cx-pcx) <= 1 && rcAbs(cy-pcz) <= 1) + if (array.size() < 3) { - stack.resize(0); - stack.push(cx); - stack.push(cy); - stack.push(ci); + ctx->log(RC_LOG_WARNING, "Walk towards polygon center failed to reach center"); break; } - + + ci = array.pop(); + cy = array.pop(); + cx = array.pop(); + + if (cx == pcx && cy == pcy) + break; + + // If we are already at the correct X-position, prefer direction + // directly towards the center in the Y-axis; otherwise prefer + // direction in the X-axis + int directDir; + if (cx == pcx) + directDir = rcGetDirForOffset(0, pcy > cy ? 1 : -1); + else + directDir = rcGetDirForOffset(pcx > cx ? 1 : -1, 0); + + // Push the direct dir last so we start with this on next iteration + rcSwap(dirs[directDir], dirs[3]); + const rcCompactSpan& cs = chf.spans[ci]; - - for (int dir = 0; dir < 4; ++dir) + for (int i = 0; i < 4; i++) { - if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; - - const int ax = cx + rcGetDirOffsetX(dir); - const int ay = cy + rcGetDirOffsetY(dir); - - if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || - ay < hp.ymin || ay >= (hp.ymin+hp.height)) + int dir = dirs[i]; + if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue; - - if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0) + + int newX = cx + rcGetDirOffsetX(dir); + int newY = cy + rcGetDirOffsetY(dir); + + int hpx = newX - hp.xmin; + int hpy = newY - hp.ymin; + if (hpx < 0 || hpx >= hp.width || hpy < 0 || hpy >= hp.height) continue; - - const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); - int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; - hp.data[idx] = 1; - - stack.push(ax); - stack.push(ay); - stack.push(ai); + if (hp.data[hpx+hpy*hp.width] != 0) + continue; + + hp.data[hpx+hpy*hp.width] = 1; + array.push(newX); + array.push(newY); + array.push((int)chf.cells[(newX+bs)+(newY+bs)*chf.width].index + rcGetCon(cs, dir)); } + + rcSwap(dirs[directDir], dirs[3]); } + array.resize(0); + // getHeightData seeds are given in coordinates with borders + array.push(cx+bs); + array.push(cy+bs); + array.push(ci); + memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); + const rcCompactSpan& cs = chf.spans[ci]; + hp.data[cx-hp.xmin+(cy-hp.ymin)*hp.width] = cs.y; +} - // Mark start locations. - for (int i = 0; i < stack.size(); i += 3) + +static void push3(rcIntArray& queue, int v1, int v2, int v3) +{ + queue.resize(queue.size() + 3); + queue[queue.size() - 3] = v1; + queue[queue.size() - 2] = v2; + queue[queue.size() - 1] = v3; +} + +static void getHeightData(rcContext* ctx, const rcCompactHeightfield& chf, + const unsigned short* poly, const int npoly, + const unsigned short* verts, const int bs, + rcHeightPatch& hp, rcIntArray& queue, + int region) +{ + // Note: Reads to the compact heightfield are offset by border size (bs) + // since border size offset is already removed from the polymesh vertices. + + queue.resize(0); + // Set all heights to RC_UNSET_HEIGHT. + memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height); + + bool empty = true; + + // We cannot sample from this poly if it was created from polys + // of different regions. If it was then it could potentially be overlapping + // with polys of that region and the heights sampled here could be wrong. + if (region != RC_MULTIPLE_REGS) { - int cx = stack[i+0]; - int cy = stack[i+1]; - int ci = stack[i+2]; - int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width; - const rcCompactSpan& cs = chf.spans[ci]; - hp.data[idx] = cs.y; + // Copy the height from the same region, and mark region borders + // as seed points to fill the rest. + for (int hy = 0; hy < hp.height; hy++) + { + int y = hp.ymin + hy + bs; + for (int hx = 0; hx < hp.width; hx++) + { + int x = hp.xmin + hx + bs; + const rcCompactCell& c = chf.cells[x + y*chf.width]; + for (int i = (int)c.index, ni = (int)(c.index + c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + if (s.reg == region) + { + // Store height + hp.data[hx + hy*hp.width] = s.y; + empty = false; + + // If any of the neighbours is not in same region, + // add the current location as flood fill start + bool border = false; + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(s, dir); + const rcCompactSpan& as = chf.spans[ai]; + if (as.reg != region) + { + border = true; + break; + } + } + } + if (border) + push3(queue, x, y, i); + break; + } + } + } + } } + // if the polygon does not contain any points from the current region (rare, but happens) + // or if it could potentially be overlapping polygons of the same region, + // then use the center as the seed point. + if (empty) + seedArrayWithPolyCenter(ctx, chf, poly, npoly, verts, bs, hp, queue); + static const int RETRACT_SIZE = 256; int head = 0; - while (head*3 < stack.size()) + // We assume the seed is centered in the polygon, so a BFS to collect + // height data will ensure we do not move onto overlapping polygons and + // sample wrong heights. + while (head*3 < queue.size()) { - int cx = stack[head*3+0]; - int cy = stack[head*3+1]; - int ci = stack[head*3+2]; + int cx = queue[head*3+0]; + int cy = queue[head*3+1]; + int ci = queue[head*3+2]; head++; if (head >= RETRACT_SIZE) { head = 0; - if (stack.size() > RETRACT_SIZE*3) - memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3)); - stack.resize(stack.size()-RETRACT_SIZE*3); + if (queue.size() > RETRACT_SIZE*3) + memmove(&queue[0], &queue[RETRACT_SIZE*3], sizeof(int)*(queue.size()-RETRACT_SIZE*3)); + queue.resize(queue.size()-RETRACT_SIZE*3); } - + const rcCompactSpan& cs = chf.spans[ci]; for (int dir = 0; dir < 4; ++dir) { @@ -895,26 +1119,23 @@ static void getHeightData(const rcCompactHeightfield& chf, const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); + const int hx = ax - hp.xmin - bs; + const int hy = ay - hp.ymin - bs; - if (ax < hp.xmin || ax >= (hp.xmin+hp.width) || - ay < hp.ymin || ay >= (hp.ymin+hp.height)) + if ((unsigned int)hx >= (unsigned int)hp.width || (unsigned int)hy >= (unsigned int)hp.height) continue; - if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != RC_UNSET_HEIGHT) + if (hp.data[hx + hy*hp.width] != RC_UNSET_HEIGHT) continue; - const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir); - + const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir); const rcCompactSpan& as = chf.spans[ai]; - int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width; - hp.data[idx] = as.y; - - stack.push(ax); - stack.push(ay); - stack.push(ai); + + hp.data[hx + hy*hp.width] = as.y; + + push3(queue, ax, ay, ai); } } - } static unsigned char getEdgeFlags(const float* va, const float* vb, @@ -924,7 +1145,7 @@ static unsigned char getEdgeFlags(const float* va, const float* vb, static const float thrSqr = rcSqr(0.001f); for (int i = 0, j = npoly-1; i < npoly; j=i++) { - if (distancePtSeg2d(va, &vpoly[j*3], &vpoly[i*3]) < thrSqr && + if (distancePtSeg2d(va, &vpoly[j*3], &vpoly[i*3]) < thrSqr && distancePtSeg2d(vb, &vpoly[j*3], &vpoly[i*3]) < thrSqr) return 1; } @@ -952,8 +1173,8 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa { rcAssert(ctx); - ctx->startTimer(RC_TIMER_BUILD_POLYMESHDETAIL); - + rcScopedTimer timer(ctx, RC_TIMER_BUILD_POLYMESHDETAIL); + if (mesh.nverts == 0 || mesh.npolys == 0) return true; @@ -962,23 +1183,24 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa const float ch = mesh.ch; const float* orig = mesh.bmin; const int borderSize = mesh.borderSize; + const int heightSearchRadius = rcMax(1, (int)ceilf(mesh.maxEdgeError)); rcIntArray edges(64); rcIntArray tris(512); - rcIntArray stack(512); + rcIntArray arr(512); rcIntArray samples(512); float verts[256*3]; rcHeightPatch hp; int nPolyVerts = 0; int maxhw = 0, maxhh = 0; - rcScopedDelete bounds = (int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP); + rcScopedDelete bounds((int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP)); if (!bounds) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' (%d).", mesh.npolys*4); return false; } - rcScopedDelete poly = (float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP); + rcScopedDelete poly((float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP)); if (!poly) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' (%d).", nvp*3); @@ -1032,10 +1254,10 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes*4); return false; } - + int vcap = nPolyVerts+nPolyVerts/2; int tcap = vcap*2; - + dmesh.nverts = 0; dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); if (!dmesh.verts) @@ -1044,7 +1266,7 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa return false; } dmesh.ntris = 0; - dmesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char*)*tcap*4, RC_ALLOC_PERM); + dmesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*tcap*4, RC_ALLOC_PERM); if (!dmesh.tris) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", tcap*4); @@ -1072,18 +1294,19 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa hp.ymin = bounds[i*4+2]; hp.width = bounds[i*4+1]-bounds[i*4+0]; hp.height = bounds[i*4+3]-bounds[i*4+2]; - getHeightData(chf, p, npoly, mesh.verts, borderSize, hp, stack); + getHeightData(ctx, chf, p, npoly, mesh.verts, borderSize, hp, arr, mesh.regs[i]); // Build detail mesh. int nverts = 0; if (!buildPolyDetail(ctx, poly, npoly, sampleDist, sampleMaxError, - chf, hp, verts, nverts, tris, + heightSearchRadius, chf, hp, + verts, nverts, tris, edges, samples)) { return false; } - + // Move detail verts to world space. for (int j = 0; j < nverts; ++j) { @@ -1098,21 +1321,21 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa poly[j*3+1] += orig[1]; poly[j*3+2] += orig[2]; } - + // Store detail submesh. const int ntris = tris.size()/4; - + dmesh.meshes[i*4+0] = (unsigned int)dmesh.nverts; dmesh.meshes[i*4+1] = (unsigned int)nverts; dmesh.meshes[i*4+2] = (unsigned int)dmesh.ntris; - dmesh.meshes[i*4+3] = (unsigned int)ntris; + dmesh.meshes[i*4+3] = (unsigned int)ntris; // Store vertices, allocate more memory if necessary. if (dmesh.nverts+nverts > vcap) { while (dmesh.nverts+nverts > vcap) vcap += 256; - + float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM); if (!newv) { @@ -1158,9 +1381,7 @@ bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompa dmesh.ntris++; } } - - ctx->stopTimer(RC_TIMER_BUILD_POLYMESHDETAIL); - + return true; } @@ -1169,12 +1390,12 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int { rcAssert(ctx); - ctx->startTimer(RC_TIMER_MERGE_POLYMESHDETAIL); - + rcScopedTimer timer(ctx, RC_TIMER_MERGE_POLYMESHDETAIL); + int maxVerts = 0; int maxTris = 0; int maxMeshes = 0; - + for (int i = 0; i < nmeshes; ++i) { if (!meshes[i]) continue; @@ -1182,7 +1403,7 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int maxTris += meshes[i]->ntris; maxMeshes += meshes[i]->nmeshes; } - + mesh.nmeshes = 0; mesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*maxMeshes*4, RC_ALLOC_PERM); if (!mesh.meshes) @@ -1190,7 +1411,7 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes*4); return false; } - + mesh.ntris = 0; mesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris*4, RC_ALLOC_PERM); if (!mesh.tris) @@ -1198,7 +1419,7 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris*4); return false; } - + mesh.nverts = 0; mesh.verts = (float*)rcAlloc(sizeof(float)*maxVerts*3, RC_ALLOC_PERM); if (!mesh.verts) @@ -1222,7 +1443,7 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int dst[3] = src[3]; mesh.nmeshes++; } - + for (int k = 0; k < dm->nverts; ++k) { rcVcopy(&mesh.verts[mesh.nverts*3], &dm->verts[k*3]); @@ -1237,9 +1458,6 @@ bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int mesh.ntris++; } } - - ctx->stopTimer(RC_TIMER_MERGE_POLYMESHDETAIL); return true; } - diff --git a/Engine/lib/recast/Recast/Source/RecastRasterization.cpp b/Engine/lib/recast/Recast/Source/RecastRasterization.cpp index d2bb7c98f1..a4cef74909 100644 --- a/Engine/lib/recast/Recast/Source/RecastRasterization.cpp +++ b/Engine/lib/recast/Recast/Source/RecastRasterization.cpp @@ -50,7 +50,7 @@ static rcSpan* allocSpan(rcHeightfield& hf) // Allocate memory for the new pool. rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); if (!pool) return 0; - pool->next = 0; + // Add the pool into the list of pools. pool->next = hf.pools; hf.pools = pool; @@ -82,7 +82,7 @@ static void freeSpan(rcHeightfield& hf, rcSpan* ptr) hf.freelist = ptr; } -static void addSpan(rcHeightfield& hf, const int x, const int y, +static bool addSpan(rcHeightfield& hf, const int x, const int y, const unsigned short smin, const unsigned short smax, const unsigned char area, const int flagMergeThr) { @@ -90,16 +90,18 @@ static void addSpan(rcHeightfield& hf, const int x, const int y, int idx = x + y*hf.width; rcSpan* s = allocSpan(hf); + if (!s) + return false; s->smin = smin; s->smax = smax; s->area = area; s->next = 0; - // Empty cell, add he first span. + // Empty cell, add the first span. if (!hf.spans[idx]) { hf.spans[idx] = s; - return; + return true; } rcSpan* prev = 0; rcSpan* cur = hf.spans[idx]; @@ -152,6 +154,8 @@ static void addSpan(rcHeightfield& hf, const int x, const int y, s->next = hf.spans[idx]; hf.spans[idx] = s; } + + return true; } /// @par @@ -161,45 +165,80 @@ static void addSpan(rcHeightfield& hf, const int x, const int y, /// from the existing span, the span flags are merged. /// /// @see rcHeightfield, rcSpan. -void rcAddSpan(rcContext* /*ctx*/, rcHeightfield& hf, const int x, const int y, +bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, const unsigned short smin, const unsigned short smax, const unsigned char area, const int flagMergeThr) { -// rcAssert(ctx); - addSpan(hf, x,y, smin, smax, area, flagMergeThr); + rcAssert(ctx); + + if (!addSpan(hf, x, y, smin, smax, area, flagMergeThr)) + { + ctx->log(RC_LOG_ERROR, "rcAddSpan: Out of memory."); + return false; + } + + return true; } -static int clipPoly(const float* in, int n, float* out, float pnx, float pnz, float pd) +// divides a convex polygons into two convex polygons on both sides of a line +static void dividePoly(const float* in, int nin, + float* out1, int* nout1, + float* out2, int* nout2, + float x, int axis) { float d[12]; - for (int i = 0; i < n; ++i) - d[i] = pnx*in[i*3+0] + pnz*in[i*3+2] + pd; - - int m = 0; - for (int i = 0, j = n-1; i < n; j=i, ++i) + for (int i = 0; i < nin; ++i) + d[i] = x - in[i*3+axis]; + + int m = 0, n = 0; + for (int i = 0, j = nin-1; i < nin; j=i, ++i) { bool ina = d[j] >= 0; bool inb = d[i] >= 0; if (ina != inb) { float s = d[j] / (d[j] - d[i]); - out[m*3+0] = in[j*3+0] + (in[i*3+0] - in[j*3+0])*s; - out[m*3+1] = in[j*3+1] + (in[i*3+1] - in[j*3+1])*s; - out[m*3+2] = in[j*3+2] + (in[i*3+2] - in[j*3+2])*s; + out1[m*3+0] = in[j*3+0] + (in[i*3+0] - in[j*3+0])*s; + out1[m*3+1] = in[j*3+1] + (in[i*3+1] - in[j*3+1])*s; + out1[m*3+2] = in[j*3+2] + (in[i*3+2] - in[j*3+2])*s; + rcVcopy(out2 + n*3, out1 + m*3); m++; + n++; + // add the i'th point to the right polygon. Do NOT add points that are on the dividing line + // since these were already added above + if (d[i] > 0) + { + rcVcopy(out1 + m*3, in + i*3); + m++; + } + else if (d[i] < 0) + { + rcVcopy(out2 + n*3, in + i*3); + n++; + } } - if (inb) + else // same side { - out[m*3+0] = in[i*3+0]; - out[m*3+1] = in[i*3+1]; - out[m*3+2] = in[i*3+2]; - m++; + // add the i'th point to the right polygon. Addition is done even for points on the dividing line + if (d[i] >= 0) + { + rcVcopy(out1 + m*3, in + i*3); + m++; + if (d[i] != 0) + continue; + } + rcVcopy(out2 + n*3, in + i*3); + n++; } } - return m; + + *nout1 = m; + *nout2 = n; } -static void rasterizeTri(const float* v0, const float* v1, const float* v2, + + +static bool rasterizeTri(const float* v0, const float* v1, const float* v2, const unsigned char area, rcHeightfield& hf, const float* bmin, const float* bmax, const float cs, const float ics, const float ich, @@ -220,50 +259,59 @@ static void rasterizeTri(const float* v0, const float* v1, const float* v2, // If the triangle does not touch the bbox of the heightfield, skip the triagle. if (!overlapBounds(bmin, bmax, tmin, tmax)) - return; + return true; - // Calculate the footpring of the triangle on the grid. - int x0 = (int)((tmin[0] - bmin[0])*ics); + // Calculate the footprint of the triangle on the grid's y-axis int y0 = (int)((tmin[2] - bmin[2])*ics); - int x1 = (int)((tmax[0] - bmin[0])*ics); int y1 = (int)((tmax[2] - bmin[2])*ics); - x0 = rcClamp(x0, 0, w-1); y0 = rcClamp(y0, 0, h-1); - x1 = rcClamp(x1, 0, w-1); y1 = rcClamp(y1, 0, h-1); // Clip the triangle into all grid cells it touches. - float in[7*3], out[7*3], inrow[7*3]; + float buf[7*3*4]; + float *in = buf, *inrow = buf+7*3, *p1 = inrow+7*3, *p2 = p1+7*3; + + rcVcopy(&in[0], v0); + rcVcopy(&in[1*3], v1); + rcVcopy(&in[2*3], v2); + int nvrow, nvIn = 3; for (int y = y0; y <= y1; ++y) { - // Clip polygon to row. - rcVcopy(&in[0], v0); - rcVcopy(&in[1*3], v1); - rcVcopy(&in[2*3], v2); - int nvrow = 3; + // Clip polygon to row. Store the remaining polygon as well const float cz = bmin[2] + y*cs; - nvrow = clipPoly(in, nvrow, out, 0, 1, -cz); - if (nvrow < 3) continue; - nvrow = clipPoly(out, nvrow, inrow, 0, -1, cz+cs); + dividePoly(in, nvIn, inrow, &nvrow, p1, &nvIn, cz+cs, 2); + rcSwap(in, p1); if (nvrow < 3) continue; + // find the horizontal bounds in the row + float minX = inrow[0], maxX = inrow[0]; + for (int i=1; i inrow[i*3]) minX = inrow[i*3]; + if (maxX < inrow[i*3]) maxX = inrow[i*3]; + } + int x0 = (int)((minX - bmin[0])*ics); + int x1 = (int)((maxX - bmin[0])*ics); + x0 = rcClamp(x0, 0, w-1); + x1 = rcClamp(x1, 0, w-1); + + int nv, nv2 = nvrow; + for (int x = x0; x <= x1; ++x) { - // Clip polygon to column. - int nv = nvrow; + // Clip polygon to column. store the remaining polygon as well const float cx = bmin[0] + x*cs; - nv = clipPoly(inrow, nv, out, 1, 0, -cx); - if (nv < 3) continue; - nv = clipPoly(out, nv, in, -1, 0, cx+cs); + dividePoly(inrow, nv2, p1, &nv, p2, &nv2, cx+cs, 0); + rcSwap(inrow, p2); if (nv < 3) continue; // Calculate min and max of the span. - float smin = in[1], smax = in[1]; + float smin = p1[1], smax = p1[1]; for (int i = 1; i < nv; ++i) { - smin = rcMin(smin, in[i*3+1]); - smax = rcMax(smax, in[i*3+1]); + smin = rcMin(smin, p1[i*3+1]); + smax = rcMax(smax, p1[i*3+1]); } smin -= bmin[1]; smax -= bmin[1]; @@ -278,9 +326,12 @@ static void rasterizeTri(const float* v0, const float* v1, const float* v2, unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT); unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); - addSpan(hf, x, y, ismin, ismax, area, flagMergeThr); + if (!addSpan(hf, x, y, ismin, ismax, area, flagMergeThr)) + return false; } } + + return true; } /// @par @@ -288,19 +339,23 @@ static void rasterizeTri(const float* v0, const float* v1, const float* v2, /// No spans will be added if the triangle does not overlap the heightfield grid. /// /// @see rcHeightfield -void rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, +bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, const unsigned char area, rcHeightfield& solid, const int flagMergeThr) { rcAssert(ctx); - ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); const float ics = 1.0f/solid.cs; const float ich = 1.0f/solid.ch; - rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + if (!rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + { + ctx->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory."); + return false; + } - ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); + return true; } /// @par @@ -308,13 +363,13 @@ void rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const /// Spans will only be added for triangles that overlap the heightfield grid. /// /// @see rcHeightfield -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, +bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, const int* tris, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr) { rcAssert(ctx); - ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); const float ics = 1.0f/solid.cs; const float ich = 1.0f/solid.ch; @@ -325,10 +380,14 @@ void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, const float* v1 = &verts[tris[i*3+1]*3]; const float* v2 = &verts[tris[i*3+2]*3]; // Rasterize. - rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + { + ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + return false; + } } - - ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); + + return true; } /// @par @@ -336,13 +395,13 @@ void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, /// Spans will only be added for triangles that overlap the heightfield grid. /// /// @see rcHeightfield -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, +bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, const unsigned short* tris, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr) { rcAssert(ctx); - ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); const float ics = 1.0f/solid.cs; const float ich = 1.0f/solid.ch; @@ -353,10 +412,14 @@ void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, const float* v1 = &verts[tris[i*3+1]*3]; const float* v2 = &verts[tris[i*3+2]*3]; // Rasterize. - rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + { + ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + return false; + } } - - ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); + + return true; } /// @par @@ -364,12 +427,12 @@ void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/, /// Spans will only be added for triangles that overlap the heightfield grid. /// /// @see rcHeightfield -void rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, +bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr) { rcAssert(ctx); - ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES); + rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); const float ics = 1.0f/solid.cs; const float ich = 1.0f/solid.ch; @@ -380,8 +443,12 @@ void rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned cha const float* v1 = &verts[(i*3+1)*3]; const float* v2 = &verts[(i*3+2)*3]; // Rasterize. - rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); + if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) + { + ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); + return false; + } } - - ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); + + return true; } diff --git a/Engine/lib/recast/Recast/Source/RecastRegion.cpp b/Engine/lib/recast/Recast/Source/RecastRegion.cpp index 76e631cc5f..54acf4b736 100644 --- a/Engine/lib/recast/Recast/Source/RecastRegion.cpp +++ b/Engine/lib/recast/Recast/Source/RecastRegion.cpp @@ -286,7 +286,10 @@ static bool floodRegion(int x, int y, int i, if (nr & RC_BORDER_REG) // Do not take borders into account. continue; if (nr != 0 && nr != r) + { ar = nr; + break; + } const rcCompactSpan& as = chf.spans[ai]; @@ -300,7 +303,10 @@ static bool floodRegion(int x, int y, int i, continue; unsigned short nr2 = srcReg[ai2]; if (nr2 != 0 && nr2 != r) + { ar = nr2; + break; + } } } } @@ -309,6 +315,7 @@ static bool floodRegion(int x, int y, int i, srcReg[ci] = 0; continue; } + count++; // Expand neighbours. @@ -340,30 +347,44 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, rcCompactHeightfield& chf, unsigned short* srcReg, unsigned short* srcDist, unsigned short* dstReg, unsigned short* dstDist, - rcIntArray& stack) + rcIntArray& stack, + bool fillStack) { const int w = chf.width; const int h = chf.height; - // Find cells revealed by the raised level. - stack.resize(0); - for (int y = 0; y < h; ++y) + if (fillStack) { - for (int x = 0; x < w; ++x) + // Find cells revealed by the raised level. + stack.resize(0); + for (int y = 0; y < h; ++y) { - const rcCompactCell& c = chf.cells[x+y*w]; - for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + for (int x = 0; x < w; ++x) { - if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA) + const rcCompactCell& c = chf.cells[x+y*w]; + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { - stack.push(x); - stack.push(y); - stack.push(i); + if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA) + { + stack.push(x); + stack.push(y); + stack.push(i); + } } } } } - + else // use cells in the input stack + { + // mark all cells which already have a region + for (int j=0; j 0) { @@ -434,6 +455,61 @@ static unsigned short* expandRegions(int maxIter, unsigned short level, } + +static void sortCellsByLevel(unsigned short startLevel, + rcCompactHeightfield& chf, + unsigned short* srcReg, + unsigned int nbStacks, rcIntArray* stacks, + unsigned short loglevelsPerStack) // the levels per stack (2 in our case) as a bit shift +{ + const int w = chf.width; + const int h = chf.height; + startLevel = startLevel >> loglevelsPerStack; + + for (unsigned int j=0; j> loglevelsPerStack; + int sId = startLevel - level; + if (sId >= (int)nbStacks) + continue; + if (sId < 0) + sId = 0; + + stacks[sId].push(x); + stacks[sId].push(y); + stacks[sId].push(i); + } + } + } +} + + +static void appendStacks(rcIntArray& srcStack, rcIntArray& dstStack, + unsigned short* srcReg) +{ + for (int j=0; jlog(RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (%d).", nreg); + ctx->log(RC_LOG_ERROR, "mergeAndFilterRegions: Out of memory 'regions' (%d).", nreg); return false; } @@ -728,7 +812,6 @@ static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegio rcRegion& reg = regions[r]; reg.spanCount++; - // Update floors. for (int j = (int)c.index; j < ni; ++j) { @@ -736,6 +819,8 @@ static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegio unsigned short floorId = srcReg[j]; if (floorId == 0 || floorId >= nreg) continue; + if (floorId == r) + reg.overlap = true; addUniqueFloorRegion(reg, floorId); } @@ -831,7 +916,7 @@ static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegio } } } - + // Merge too small regions to neighbour regions. int mergeCount = 0 ; do @@ -841,7 +926,9 @@ static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegio { rcRegion& reg = regions[i]; if (reg.id == 0 || (reg.id & RC_BORDER_REG)) - continue; + continue; + if (reg.overlap) + continue; if (reg.spanCount == 0) continue; @@ -858,7 +945,7 @@ static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegio { if (reg.connections[j] & RC_BORDER_REG) continue; rcRegion& mreg = regions[reg.connections[j]]; - if (mreg.id == 0 || (mreg.id & RC_BORDER_REG)) continue; + if (mreg.id == 0 || (mreg.id & RC_BORDER_REG) || mreg.overlap) continue; if (mreg.spanCount < smallest && canMergeWithRegion(reg, mreg) && canMergeWithRegion(mreg, reg)) @@ -922,6 +1009,224 @@ static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegio } maxRegionId = regIdGen; + // Remap regions. + for (int i = 0; i < chf.spanCount; ++i) + { + if ((srcReg[i] & RC_BORDER_REG) == 0) + srcReg[i] = regions[srcReg[i]].id; + } + + // Return regions that we found to be overlapping. + for (int i = 0; i < nreg; ++i) + if (regions[i].overlap) + overlaps.push(regions[i].id); + + for (int i = 0; i < nreg; ++i) + regions[i].~rcRegion(); + rcFree(regions); + + + return true; +} + + +static void addUniqueConnection(rcRegion& reg, int n) +{ + for (int i = 0; i < reg.connections.size(); ++i) + if (reg.connections[i] == n) + return; + reg.connections.push(n); +} + +static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea, + unsigned short& maxRegionId, + rcCompactHeightfield& chf, + unsigned short* srcReg, rcIntArray& /*overlaps*/) +{ + const int w = chf.width; + const int h = chf.height; + + const int nreg = maxRegionId+1; + rcRegion* regions = (rcRegion*)rcAlloc(sizeof(rcRegion)*nreg, RC_ALLOC_TEMP); + if (!regions) + { + ctx->log(RC_LOG_ERROR, "mergeAndFilterLayerRegions: Out of memory 'regions' (%d).", nreg); + return false; + } + + // Construct regions + for (int i = 0; i < nreg; ++i) + new(®ions[i]) rcRegion((unsigned short)i); + + // Find region neighbours and overlapping regions. + rcIntArray lregs(32); + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + + lregs.resize(0); + + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + const unsigned short ri = srcReg[i]; + if (ri == 0 || ri >= nreg) continue; + rcRegion& reg = regions[ri]; + + reg.spanCount++; + + reg.ymin = rcMin(reg.ymin, s.y); + reg.ymax = rcMax(reg.ymax, s.y); + + // Collect all region layers. + lregs.push(ri); + + // Update neighbours + for (int dir = 0; dir < 4; ++dir) + { + if (rcGetCon(s, dir) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(dir); + const int ay = y + rcGetDirOffsetY(dir); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); + const unsigned short rai = srcReg[ai]; + if (rai > 0 && rai < nreg && rai != ri) + addUniqueConnection(reg, rai); + if (rai & RC_BORDER_REG) + reg.connectsToBorder = true; + } + } + + } + + // Update overlapping regions. + for (int i = 0; i < lregs.size()-1; ++i) + { + for (int j = i+1; j < lregs.size(); ++j) + { + if (lregs[i] != lregs[j]) + { + rcRegion& ri = regions[lregs[i]]; + rcRegion& rj = regions[lregs[j]]; + addUniqueFloorRegion(ri, lregs[j]); + addUniqueFloorRegion(rj, lregs[i]); + } + } + } + + } + } + + // Create 2D layers from regions. + unsigned short layerId = 1; + + for (int i = 0; i < nreg; ++i) + regions[i].id = 0; + + // Merge montone regions to create non-overlapping areas. + rcIntArray stack(32); + for (int i = 1; i < nreg; ++i) + { + rcRegion& root = regions[i]; + // Skip already visited. + if (root.id != 0) + continue; + + // Start search. + root.id = layerId; + + stack.resize(0); + stack.push(i); + + while (stack.size() > 0) + { + // Pop front + rcRegion& reg = regions[stack[0]]; + for (int j = 0; j < stack.size()-1; ++j) + stack[j] = stack[j+1]; + stack.resize(stack.size()-1); + + const int ncons = (int)reg.connections.size(); + for (int j = 0; j < ncons; ++j) + { + const int nei = reg.connections[j]; + rcRegion& regn = regions[nei]; + // Skip already visited. + if (regn.id != 0) + continue; + // Skip if the neighbour is overlapping root region. + bool overlap = false; + for (int k = 0; k < root.floors.size(); k++) + { + if (root.floors[k] == nei) + { + overlap = true; + break; + } + } + if (overlap) + continue; + + // Deepen + stack.push(nei); + + // Mark layer id + regn.id = layerId; + // Merge current layers to root. + for (int k = 0; k < regn.floors.size(); ++k) + addUniqueFloorRegion(root, regn.floors[k]); + root.ymin = rcMin(root.ymin, regn.ymin); + root.ymax = rcMax(root.ymax, regn.ymax); + root.spanCount += regn.spanCount; + regn.spanCount = 0; + root.connectsToBorder = root.connectsToBorder || regn.connectsToBorder; + } + } + + layerId++; + } + + // Remove small regions + for (int i = 0; i < nreg; ++i) + { + if (regions[i].spanCount > 0 && regions[i].spanCount < minRegionArea && !regions[i].connectsToBorder) + { + unsigned short reg = regions[i].id; + for (int j = 0; j < nreg; ++j) + if (regions[j].id == reg) + regions[j].id = 0; + } + } + + // Compress region Ids. + for (int i = 0; i < nreg; ++i) + { + regions[i].remap = false; + if (regions[i].id == 0) continue; // Skip nil regions. + if (regions[i].id & RC_BORDER_REG) continue; // Skip external regions. + regions[i].remap = true; + } + + unsigned short regIdGen = 0; + for (int i = 0; i < nreg; ++i) + { + if (!regions[i].remap) + continue; + unsigned short oldId = regions[i].id; + unsigned short newId = ++regIdGen; + for (int j = i; j < nreg; ++j) + { + if (regions[j].id == oldId) + { + regions[j].id = newId; + regions[j].remap = false; + } + } + } + maxRegionId = regIdGen; + // Remap regions. for (int i = 0; i < chf.spanCount; ++i) { @@ -936,6 +1241,8 @@ static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegio return true; } + + /// @par /// /// This is usually the second to the last step in creating a fully built @@ -950,7 +1257,7 @@ bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf) { rcAssert(ctx); - ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD); + rcScopedTimer timer(ctx, RC_TIMER_BUILD_DISTANCEFIELD); if (chf.dist) { @@ -974,25 +1281,23 @@ bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf) unsigned short maxDist = 0; - ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST); - - calculateDistanceField(chf, src, maxDist); - chf.maxDistance = maxDist; - - ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST); - - ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR); - - // Blur - if (boxBlur(chf, 1, src, dst) != src) - rcSwap(src, dst); - - // Store distance. - chf.dist = src; - - ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR); + { + rcScopedTimer timerDist(ctx, RC_TIMER_BUILD_DISTANCEFIELD_DIST); - ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD); + calculateDistanceField(chf, src, maxDist); + chf.maxDistance = maxDist; + } + + { + rcScopedTimer timerBlur(ctx, RC_TIMER_BUILD_DISTANCEFIELD_BLUR); + + // Blur + if (boxBlur(chf, 1, src, dst) != src) + rcSwap(src, dst); + + // Store distance. + chf.dist = src; + } rcFree(dst); @@ -1052,13 +1357,13 @@ bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, { rcAssert(ctx); - ctx->startTimer(RC_TIMER_BUILD_REGIONS); + rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS); const int w = chf.width; const int h = chf.height; unsigned short id = 1; - rcScopedDelete srcReg = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); + rcScopedDelete srcReg((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP)); if (!srcReg) { ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' (%d).", chf.spanCount); @@ -1067,7 +1372,7 @@ bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, memset(srcReg,0,sizeof(unsigned short)*chf.spanCount); const int nsweeps = rcMax(chf.width,chf.height); - rcScopedDelete sweeps = (rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan)*nsweeps, RC_ALLOC_TEMP); + rcScopedDelete sweeps((rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan)*nsweeps, RC_ALLOC_TEMP)); if (!sweeps) { ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' (%d).", nsweeps); @@ -1181,20 +1486,22 @@ bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, } } - ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); - // Filter out small regions. - chf.maxRegions = id; - if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) - return false; + { + rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER); - ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); + // Merge regions and filter out small regions. + rcIntArray overlaps; + chf.maxRegions = id; + if (!mergeAndFilterRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg, overlaps)) + return false; + + // Monotone partitioning does not generate overlapping regions. + } // Store the result out. for (int i = 0; i < chf.spanCount; ++i) chf.spans[i].reg = srcReg[i]; - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS); return true; } @@ -1223,12 +1530,12 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, { rcAssert(ctx); - ctx->startTimer(RC_TIMER_BUILD_REGIONS); + rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS); const int w = chf.width; const int h = chf.height; - rcScopedDelete buf = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP); + rcScopedDelete buf((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP)); if (!buf) { ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4); @@ -1236,7 +1543,13 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, } ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); - + + const int LOG_NB_STACKS = 3; + const int NB_STACKS = 1 << LOG_NB_STACKS; + rcIntArray lvlStacks[NB_STACKS]; + for (int i=0; i 0xFFFB) + { + ctx->log(RC_LOG_ERROR, "rcBuildRegions: Region ID overflow"); + return false; + } + // Paint regions paintRectRegion(0, bw, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++; @@ -1271,44 +1591,60 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, chf.borderSize = borderSize; } + int sId = -1; while (level > 0) { level = level >= 2 ? level-2 : 0; - - ctx->startTimer(RC_TIMER_BUILD_REGIONS_EXPAND); - - // Expand current regions until no empty connected cells found. - if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) + sId = (sId+1) & (NB_STACKS-1); + +// ctx->startTimer(RC_TIMER_DIVIDE_TO_LEVELS); + + if (sId == 0) + sortCellsByLevel(level, chf, srcReg, NB_STACKS, lvlStacks, 1); + else + appendStacks(lvlStacks[sId-1], lvlStacks[sId], srcReg); // copy left overs from last level + +// ctx->stopTimer(RC_TIMER_DIVIDE_TO_LEVELS); + { - rcSwap(srcReg, dstReg); - rcSwap(srcDist, dstDist); + rcScopedTimer timerExpand(ctx, RC_TIMER_BUILD_REGIONS_EXPAND); + + // Expand current regions until no empty connected cells found. + if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, lvlStacks[sId], false) != srcReg) + { + rcSwap(srcReg, dstReg); + rcSwap(srcDist, dstDist); + } } - ctx->stopTimer(RC_TIMER_BUILD_REGIONS_EXPAND); - - ctx->startTimer(RC_TIMER_BUILD_REGIONS_FLOOD); - - // Mark new regions with IDs. - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) + rcScopedTimer timerFloor(ctx, RC_TIMER_BUILD_REGIONS_FLOOD); + + // Mark new regions with IDs. + for (int j = 0; j= 0 && srcReg[i] == 0) { - if (chf.dist[i] < level || srcReg[i] != 0 || chf.areas[i] == RC_NULL_AREA) - continue; if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) + { + if (regionId == 0xFFFF) + { + ctx->log(RC_LOG_ERROR, "rcBuildRegions: Region ID overflow"); + return false; + } + regionId++; + } } } } - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FLOOD); } // Expand current regions until no empty connected cells found. - if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) + if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack, true) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); @@ -1316,22 +1652,179 @@ bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf, ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); - ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); - - // Filter out small regions. - chf.maxRegions = regionId; - if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) - return false; - - ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); + { + rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER); + + // Merge regions and filter out smalle regions. + rcIntArray overlaps; + chf.maxRegions = regionId; + if (!mergeAndFilterRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg, overlaps)) + return false; + + // If overlapping regions were found during merging, split those regions. + if (overlaps.size() > 0) + { + ctx->log(RC_LOG_ERROR, "rcBuildRegions: %d overlapping regions.", overlaps.size()); + } + } // Write the result out. for (int i = 0; i < chf.spanCount; ++i) chf.spans[i].reg = srcReg[i]; - ctx->stopTimer(RC_TIMER_BUILD_REGIONS); - return true; } +bool rcBuildLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, + const int borderSize, const int minRegionArea) +{ + rcAssert(ctx); + + rcScopedTimer timer(ctx, RC_TIMER_BUILD_REGIONS); + + const int w = chf.width; + const int h = chf.height; + unsigned short id = 1; + + rcScopedDelete srcReg((unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP)); + if (!srcReg) + { + ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' (%d).", chf.spanCount); + return false; + } + memset(srcReg,0,sizeof(unsigned short)*chf.spanCount); + + const int nsweeps = rcMax(chf.width,chf.height); + rcScopedDelete sweeps((rcSweepSpan*)rcAlloc(sizeof(rcSweepSpan)*nsweeps, RC_ALLOC_TEMP)); + if (!sweeps) + { + ctx->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' (%d).", nsweeps); + return false; + } + + + // Mark border regions. + if (borderSize > 0) + { + // Make sure border will not overflow. + const int bw = rcMin(w, borderSize); + const int bh = rcMin(h, borderSize); + // Paint regions + paintRectRegion(0, bw, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; + paintRectRegion(w-bw, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++; + paintRectRegion(0, w, 0, bh, id|RC_BORDER_REG, chf, srcReg); id++; + paintRectRegion(0, w, h-bh, h, id|RC_BORDER_REG, chf, srcReg); id++; + + chf.borderSize = borderSize; + } + + rcIntArray prev(256); + + // Sweep one line at a time. + for (int y = borderSize; y < h-borderSize; ++y) + { + // Collect spans from this row. + prev.resize(id+1); + memset(&prev[0],0,sizeof(int)*id); + unsigned short rid = 1; + + for (int x = borderSize; x < w-borderSize; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + const rcCompactSpan& s = chf.spans[i]; + if (chf.areas[i] == RC_NULL_AREA) continue; + + // -x + unsigned short previd = 0; + if (rcGetCon(s, 0) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(0); + const int ay = y + rcGetDirOffsetY(0); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); + if ((srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) + previd = srcReg[ai]; + } + + if (!previd) + { + previd = rid++; + sweeps[previd].rid = previd; + sweeps[previd].ns = 0; + sweeps[previd].nei = 0; + } + + // -y + if (rcGetCon(s,3) != RC_NOT_CONNECTED) + { + const int ax = x + rcGetDirOffsetX(3); + const int ay = y + rcGetDirOffsetY(3); + const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); + if (srcReg[ai] && (srcReg[ai] & RC_BORDER_REG) == 0 && chf.areas[i] == chf.areas[ai]) + { + unsigned short nr = srcReg[ai]; + if (!sweeps[previd].nei || sweeps[previd].nei == nr) + { + sweeps[previd].nei = nr; + sweeps[previd].ns++; + prev[nr]++; + } + else + { + sweeps[previd].nei = RC_NULL_NEI; + } + } + } + + srcReg[i] = previd; + } + } + + // Create unique ID. + for (int i = 1; i < rid; ++i) + { + if (sweeps[i].nei != RC_NULL_NEI && sweeps[i].nei != 0 && + prev[sweeps[i].nei] == (int)sweeps[i].ns) + { + sweeps[i].id = sweeps[i].nei; + } + else + { + sweeps[i].id = id++; + } + } + + // Remap IDs + for (int x = borderSize; x < w-borderSize; ++x) + { + const rcCompactCell& c = chf.cells[x+y*w]; + + for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) + { + if (srcReg[i] > 0 && srcReg[i] < rid) + srcReg[i] = sweeps[srcReg[i]].id; + } + } + } + + + { + rcScopedTimer timerFilter(ctx, RC_TIMER_BUILD_REGIONS_FILTER); + + // Merge monotone regions to layers and remove small regions. + rcIntArray overlaps; + chf.maxRegions = id; + if (!mergeAndFilterLayerRegions(ctx, minRegionArea, chf.maxRegions, chf, srcReg, overlaps)) + return false; + } + + + // Store the result out. + for (int i = 0; i < chf.spanCount; ++i) + chf.spans[i].reg = srcReg[i]; + + return true; +} diff --git a/Engine/lib/recast/TODO.txt b/Engine/lib/recast/TODO.txt deleted file mode 100644 index b911c0e472..0000000000 --- a/Engine/lib/recast/TODO.txt +++ /dev/null @@ -1,20 +0,0 @@ -TODO/Roadmap - -Summer/Autumn 2009 - -- Off mesh links (jump links) -- Area annotations -- Embed extra data per polygon -- Height conforming navmesh - - -Autumn/Winter 2009/2010 - -- Detour path following -- More dynamic example with tile navmesh -- Faster small tile process - - -More info at http://digestingduck.blogspot.com/2009/07/recast-and-detour-roadmap.html - -- diff --git a/Engine/lib/recast/version.txt b/Engine/lib/recast/version.txt new file mode 100644 index 0000000000..9769f55cc6 --- /dev/null +++ b/Engine/lib/recast/version.txt @@ -0,0 +1,12 @@ + + +1.5.1 +released this on Feb 22 2016 + +Patch release; one bug has been fixed, which would cause silent failure if too many nodes were requested and used in a dtNavMeshQuery. + #179: Fail when too many nodes are requested + +1.5.0 +released this on Jan 24 2016 + +This is the first release of the Recast and Detour libraries since August 2009, containing all fixes and enhancements made since then. As you can imagine, this includes a huge number of commits, so we will forego the list of changes for this release - future releases will contain at least a summary of changes.