Recast: upgrade library.

- Upgrade Recast library to latest portable version
- Implement recast_qsort based on FreeBSD qsort.c to have 
  portable thread safe quick sort for use in conversion routine.
- Better default value for the Build Navigation Mesh operator
This commit is contained in:
Benoit Bolsee
2011-09-29 21:38:57 +00:00
parent e21e789507
commit e6a9b68c79
22 changed files with 4928 additions and 1708 deletions

View File

@@ -53,18 +53,19 @@ set(SRC
Detour/Include/DetourTileNavMeshBuilder.h
Recast/Source/Recast.cpp
Recast/Source/RecastContour.cpp
Recast/Source/RecastAlloc.cpp
Recast/Source/RecastArea.cpp
Recast/Source/RecastContour.cpp
Recast/Source/RecastFilter.cpp
Recast/Source/RecastLog.cpp
Recast/Source/RecastLayers.cpp
Recast/Source/RecastMesh.cpp
Recast/Source/RecastMeshDetail.cpp
Recast/Source/RecastRasterization.cpp
Recast/Source/RecastRegion.cpp
Recast/Source/RecastTimer.cpp
Recast/Include/Recast.h
Recast/Include/RecastLog.h
Recast/Include/RecastTimer.h
Recast/Include/RecastAlloc.h
Recast/Include/RecastAssert.h
)
blender_add_lib(extern_recastnavigation "${SRC}" "${INC}" "${INC_SYS}")

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2009 Mikko Mononen memon@inside.org
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
@@ -19,298 +19,685 @@
#ifndef RECAST_H
#define RECAST_H
// The units of the parameters are specified in parenthesis as follows:
// (vx) voxels, (wu) world units
/// The value of PI used by Recast.
static const float RC_PI = 3.14159265f;
/// Recast log categories.
/// @see rcContext
enum rcLogCategory
{
RC_LOG_PROGRESS = 1, ///< A progress log entry.
RC_LOG_WARNING, ///< A warning log entry.
RC_LOG_ERROR, ///< An error log entry.
};
/// Recast performance timer categories.
/// @see rcContext
enum rcTimerLabel
{
/// The user defined total time of the build.
RC_TIMER_TOTAL,
/// A user defined build time.
RC_TIMER_TEMP,
/// The time to rasterize the triangles. (See: #rcRasterizeTriangle)
RC_TIMER_RASTERIZE_TRIANGLES,
/// The time to build the compact heightfield. (See: #rcBuildCompactHeightfield)
RC_TIMER_BUILD_COMPACTHEIGHTFIELD,
/// The total time to build the contours. (See: #rcBuildContours)
RC_TIMER_BUILD_CONTOURS,
/// The time to trace the boundaries of the contours. (See: #rcBuildContours)
RC_TIMER_BUILD_CONTOURS_TRACE,
/// The time to simplify the contours. (See: #rcBuildContours)
RC_TIMER_BUILD_CONTOURS_SIMPLIFY,
/// The time to filter ledge spans. (See: #rcFilterLedgeSpans)
RC_TIMER_FILTER_BORDER,
/// The time to filter low height spans. (See: #rcFilterWalkableLowHeightSpans)
RC_TIMER_FILTER_WALKABLE,
/// The time to apply the median filter. (See: #rcMedianFilterWalkableArea)
RC_TIMER_MEDIAN_AREA,
/// The time to filter low obstacles. (See: #rcFilterLowHangingWalkableObstacles)
RC_TIMER_FILTER_LOW_OBSTACLES,
/// The time to build the polygon mesh. (See: #rcBuildPolyMesh)
RC_TIMER_BUILD_POLYMESH,
/// The time to merge polygon meshes. (See: #rcMergePolyMeshes)
RC_TIMER_MERGE_POLYMESH,
/// The time to erode the walkable area. (See: #rcErodeWalkableArea)
RC_TIMER_ERODE_AREA,
/// The time to mark a box area. (See: #rcMarkBoxArea)
RC_TIMER_MARK_BOX_AREA,
/// The time to mark a cylinder area. (See: #rcMarkCylinderArea)
RC_TIMER_MARK_CYLINDER_AREA,
/// The time to mark a convex polygon area. (See: #rcMarkConvexPolyArea)
RC_TIMER_MARK_CONVEXPOLY_AREA,
/// The total time to build the distance field. (See: #rcBuildDistanceField)
RC_TIMER_BUILD_DISTANCEFIELD,
/// The time to build the distances of the distance field. (See: #rcBuildDistanceField)
RC_TIMER_BUILD_DISTANCEFIELD_DIST,
/// The time to blur the distance field. (See: #rcBuildDistanceField)
RC_TIMER_BUILD_DISTANCEFIELD_BLUR,
/// The total time to build the regions. (See: #rcBuildRegions, #rcBuildRegionsMonotone)
RC_TIMER_BUILD_REGIONS,
/// The total time to apply the watershed algorithm. (See: #rcBuildRegions)
RC_TIMER_BUILD_REGIONS_WATERSHED,
/// The time to expand regions while applying the watershed algorithm. (See: #rcBuildRegions)
RC_TIMER_BUILD_REGIONS_EXPAND,
/// The time to flood regions while applying the watershed algorithm. (See: #rcBuildRegions)
RC_TIMER_BUILD_REGIONS_FLOOD,
/// The time to filter out small regions. (See: #rcBuildRegions, #rcBuildRegionsMonotone)
RC_TIMER_BUILD_REGIONS_FILTER,
/// The time to build heightfield layers. (See: #rcBuildHeightfieldLayers)
RC_TIMER_BUILD_LAYERS,
/// The time to build the polygon mesh detail. (See: #rcBuildPolyMeshDetail)
RC_TIMER_BUILD_POLYMESHDETAIL,
/// The time to merge polygon mesh details. (See: #rcMergePolyMeshDetails)
RC_TIMER_MERGE_POLYMESHDETAIL,
/// The maximum number of timers. (Used for iterating timers.)
RC_MAX_TIMERS
};
/// Provides an interface for optional logging and performance tracking of the Recast
/// build process.
/// @ingroup recast
class rcContext
{
public:
/// Contructor.
/// @param[in] state TRUE if the logging and performance timers should be enabled. [Default: true]
inline rcContext(bool state = true) : m_logEnabled(state), m_timerEnabled(state) {}
virtual ~rcContext() {}
/// Enables or disables logging.
/// @param[in] state TRUE if logging should be enabled.
inline void enableLog(bool state) { m_logEnabled = state; }
/// Clears all log entries.
inline void resetLog() { if (m_logEnabled) doResetLog(); }
/// Logs a message.
/// @param[in] category The category of the message.
/// @param[in] format The message.
void log(const rcLogCategory category, const char* format, ...);
/// Enables or disables the performance timers.
/// @param[in] state TRUE if timers should be enabled.
inline void enableTimer(bool state) { m_timerEnabled = state; }
/// Clears all peformance timers. (Resets all to unused.)
inline void resetTimers() { if (m_timerEnabled) doResetTimers(); }
/// Starts the specified performance timer.
/// @param label The category of timer.
inline void startTimer(const rcTimerLabel label) { if (m_timerEnabled) doStartTimer(label); }
/// Stops the specified performance timer.
/// @param label The category of the timer.
inline void stopTimer(const rcTimerLabel label) { if (m_timerEnabled) doStopTimer(label); }
/// Returns the total accumulated time of the specified performance timer.
/// @param label The category of the timer.
/// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started.
inline int getAccumulatedTime(const rcTimerLabel label) const { return m_timerEnabled ? doGetAccumulatedTime(label) : -1; }
protected:
/// Clears all log entries.
virtual void doResetLog() {}
/// Logs a message.
/// @param[in] category The category of the message.
/// @param[in] msg The formatted message.
/// @param[in] len The length of the formatted message.
virtual void doLog(const rcLogCategory /*category*/, const char* /*msg*/, const int /*len*/) {}
/// Clears all timers. (Resets all to unused.)
virtual void doResetTimers() {}
/// Starts the specified performance timer.
/// @param[in] label The category of timer.
virtual void doStartTimer(const rcTimerLabel /*label*/) {}
/// Stops the specified performance timer.
/// @param[in] label The category of the timer.
virtual void doStopTimer(const rcTimerLabel /*label*/) {}
/// Returns the total accumulated time of the specified performance timer.
/// @param[in] label The category of the timer.
/// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started.
virtual int doGetAccumulatedTime(const rcTimerLabel /*label*/) const { return -1; }
/// True if logging is enabled.
bool m_logEnabled;
/// True if the performance timers are enabled.
bool m_timerEnabled;
};
/// Specifies a configuration to use when performing Recast builds.
/// @ingroup recast
struct rcConfig
{
int width, height; // Dimensions of the rasterized heighfield (vx)
int tileSize; // Width and Height of a tile (vx)
int borderSize; // Non-navigable Border around the heightfield (vx)
float cs, ch; // Grid cell size and height (wu)
float bmin[3], bmax[3]; // Grid bounds (wu)
float walkableSlopeAngle; // Maximum walkble slope angle in degrees.
int walkableHeight; // Minimum height where the agent can still walk (vx)
int walkableClimb; // Maximum height between grid cells the agent can climb (vx)
int walkableRadius; // Radius of the agent in cells (vx)
int maxEdgeLen; // Maximum contour edge length (vx)
float maxSimplificationError; // Maximum distance error from contour to cells (vx)
int minRegionSize; // Minimum regions size. Smaller regions will be deleted (vx)
int mergeRegionSize; // Minimum regions size. Smaller regions will be merged (vx)
int maxVertsPerPoly; // Max number of vertices per polygon
float detailSampleDist; // Detail mesh sample spacing.
float detailSampleMaxError; // Detail mesh simplification max sample error.
/// The width of the field along the x-axis. [Limit: >= 0] [Units: vx]
int width;
/// The height of the field along the z-axis. [Limit: >= 0] [Units: vx]
int height;
/// The width/height size of tile's on the xz-plane. [Limit: >= 0] [Units: vx]
int tileSize;
/// The size of the non-navigable border around the heightfield. [Limit: >=0] [Units: vx]
int borderSize;
/// The xz-plane cell size to use for fields. [Limit: > 0] [Units: wu]
float cs;
/// The y-axis cell size to use for fields. [Limit: > 0] [Units: wu]
float ch;
/// The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu]
float bmin[3];
/// The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu]
float bmax[3];
/// The maximum slope that is considered walkable. [Limits: 0 <= value < 90] [Units: Degrees]
float walkableSlopeAngle;
/// Minimum floor to 'ceiling' height that will still allow the floor area to
/// be considered walkable. [Limit: >= 3] [Units: vx]
int walkableHeight;
/// Maximum ledge height that is considered to still be traversable. [Limit: >=0] [Units: vx]
int walkableClimb;
/// The distance to erode/shrink the walkable area of the heightfield away from
/// obstructions. [Limit: >=0] [Units: vx]
int walkableRadius;
/// The maximum allowed length for contour edges along the border of the mesh. [Limit: >=0] [Units: vx]
int maxEdgeLen;
/// The maximum distance a simplfied contour's border edges should deviate
/// the original raw contour. [Limit: >=0] [Units: wu]
float maxSimplificationError;
/// The minimum number of cells allowed to form isolated island areas. [Limit: >=0] [Units: vx]
int minRegionArea;
/// Any regions with a span count smaller than this value will, if possible,
/// be merged with larger regions. [Limit: >=0] [Units: vx]
int mergeRegionArea;
/// The maximum number of vertices allowed for polygons generated during the
/// contour to polygon conversion process. [Limit: >= 3]
int maxVertsPerPoly;
/// Sets the sampling distance to use when generating the detail mesh.
/// (For height detail only.) [Limits: 0 or >= 0.9] [Units: wu]
float detailSampleDist;
/// The maximum distance the detail mesh surface should deviate from heightfield
/// data. (For height detail only.) [Limit: >=0] [Units: wu]
float detailSampleMaxError;
};
// Heightfield span.
struct rcSpan
{
unsigned int smin : 15; // Span min height.
unsigned int smax : 15; // Span max height.
unsigned int flags : 2; // Span flags.
rcSpan* next; // Next span in column.
};
/// 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<<RC_SPAN_HEIGHT_BITS)-1;
/// The number of spans allocated per span spool.
/// @see rcSpanPool
static const int RC_SPANS_PER_POOL = 2048;
// Memory pool used for quick span allocation.
/// Represents a span in a heightfield.
/// @see rcHeightfield
struct rcSpan
{
unsigned int smin : 13; ///< The lower limit of the span. [Limit: < #smax]
unsigned int smax : 13; ///< The upper limit of the span. [Limit: <= #RC_SPAN_MAX_HEIGHT]
unsigned int area : 6; ///< The area id assigned to the span.
rcSpan* next; ///< The next span higher up in column.
};
/// A memory pool used for quick allocation of spans within a heightfield.
/// @see rcHeightfield
struct rcSpanPool
{
rcSpanPool* next; // Pointer to next pool.
rcSpan items[1]; // Array of spans (size RC_SPANS_PER_POOL).
rcSpanPool* next; ///< The next span pool.
rcSpan items[RC_SPANS_PER_POOL]; ///< Array of spans in the pool.
};
// Dynamic span-heightfield.
/// A dynamic heightfield representing obstructed space.
/// @ingroup recast
struct rcHeightfield
{
inline rcHeightfield() : width(0), height(0), spans(0), pools(0), freelist(0) {}
inline ~rcHeightfield()
{
// Delete span array.
delete [] spans;
// Delete span pools.
while (pools)
{
rcSpanPool* next = pools->next;
delete [] reinterpret_cast<unsigned char*>(pools);
pools = next;
}
}
int width, height; // Dimension of the heightfield.
float bmin[3], bmax[3]; // Bounding box of the heightfield
float cs, ch; // Cell size and height.
rcSpan** spans; // Heightfield of spans (width*height).
rcSpanPool* pools; // Linked list of span pools.
rcSpan* freelist; // Pointer to next free span.
int width; ///< The width of the heightfield. (Along the x-axis in cell units.)
int height; ///< The height of the heightfield. (Along the z-axis in cell units.)
float bmin[3]; ///< The minimum bounds in world space. [(x, y, z)]
float bmax[3]; ///< The maximum bounds in world space. [(x, y, z)]
float cs; ///< The size of each cell. (On the xz-plane.)
float ch; ///< The height of each cell. (The minimum increment along the y-axis.)
rcSpan** spans; ///< Heightfield of spans (width*height).
rcSpanPool* pools; ///< Linked list of span pools.
rcSpan* freelist; ///< The next free span.
};
/// Provides information on the content of a cell column in a compact heightfield.
struct rcCompactCell
{
unsigned int index : 24; // Index to first span in column.
unsigned int count : 8; // Number of spans in this column.
unsigned int index : 24; ///< Index to the first span in the column.
unsigned int count : 8; ///< Number of spans in the column.
};
/// Represents a span of unobstructed space within a compact heightfield.
struct rcCompactSpan
{
unsigned short y; // Bottom coordinate of the span.
unsigned short reg; // Region ID
unsigned short dist; // Distance to border
unsigned short con; // Connections to neighbour cells.
unsigned char h; // Height of the span.
unsigned char flags; // Flags.
unsigned short y; ///< The lower extent of the span. (Measured from the heightfield's base.)
unsigned short reg; ///< The id of the region the span belongs to. (Or zero if not in a region.)
unsigned int con : 24; ///< Packed neighbor connection data.
unsigned int h : 8; ///< The height of the span. (Measured from #y.)
};
// Compact static heightfield.
/// A compact, static heightfield representing unobstructed space.
/// @ingroup recast
struct rcCompactHeightfield
{
inline rcCompactHeightfield() : maxDistance(0), maxRegions(0), cells(0), spans(0) {}
inline ~rcCompactHeightfield() { delete [] cells; delete [] spans; }
int width, height; // Width and height of the heighfield.
int spanCount; // Number of spans in the heightfield.
int walkableHeight, walkableClimb; // Agent properties.
unsigned short maxDistance; // Maximum distance value stored in heightfield.
unsigned short maxRegions; // Maximum Region Id stored in heightfield.
float bmin[3], bmax[3]; // Bounding box of the heightfield.
float cs, ch; // Cell size and height.
rcCompactCell* cells; // Pointer to width*height cells.
rcCompactSpan* spans; // Pointer to spans.
int width; ///< The width of the heightfield. (Along the x-axis in cell units.)
int height; ///< The height of the heightfield. (Along the z-axis in cell units.)
int spanCount; ///< The number of spans in the heightfield.
int walkableHeight; ///< The walkable height used during the build of the field. (See: rcConfig::walkableHeight)
int walkableClimb; ///< The walkable climb used during the build of the field. (See: rcConfig::walkableClimb)
int borderSize; ///< The AABB border size used during the build of the field. (See: rcConfig::borderSize)
unsigned short maxDistance; ///< The maximum distance value of any span within the field.
unsigned short maxRegions; ///< The maximum region id of any span within the field.
float bmin[3]; ///< The minimum bounds in world space. [(x, y, z)]
float bmax[3]; ///< The maximum bounds in world space. [(x, y, z)]
float cs; ///< The size of each cell. (On the xz-plane.)
float ch; ///< The height of each cell. (The minimum increment along the y-axis.)
rcCompactCell* cells; ///< Array of cells. [Size: #width*#height]
rcCompactSpan* spans; ///< Array of spans. [Size: #spanCount]
unsigned short* dist; ///< Array containing border distance data. [Size: #spanCount]
unsigned char* areas; ///< Array containing area id data. [Size: #spanCount]
};
/// Represents a heightfield layer within a layer set.
/// @see rcHeightfieldLayerSet
struct rcHeightfieldLayer
{
float bmin[3]; ///< The minimum bounds in world space. [(x, y, z)]
float bmax[3]; ///< The maximum bounds in world space. [(x, y, z)]
float cs; ///< The size of each cell. (On the xz-plane.)
float ch; ///< The height of each cell. (The minimum increment along the y-axis.)
int width; ///< The width of the heightfield. (Along the x-axis in cell units.)
int height; ///< The height of the heightfield. (Along the z-axis in cell units.)
int minx; ///< The minimum x-bounds of usable data.
int maxx; ///< The maximum x-bounds of usable data.
int miny; ///< The minimum y-bounds of usable data. (Along the z-axis.)
int maxy; ///< The maximum y-bounds of usable data. (Along the z-axis.)
int hmin; ///< The minimum height bounds of usable data. (Along the y-axis.)
int hmax; ///< The maximum height bounds of usable data. (Along the y-axis.)
unsigned char* heights; ///< The heightfield. [Size: (width - borderSize*2) * (h - borderSize*2)]
unsigned char* areas; ///< Area ids. [Size: Same as #heights]
unsigned char* cons; ///< Packed neighbor connection information. [Size: Same as #heights]
};
/// Represents a set of heightfield layers.
/// @ingroup recast
/// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet
struct rcHeightfieldLayerSet
{
rcHeightfieldLayer* layers; ///< The layers in the set. [Size: #nlayers]
int nlayers; ///< The number of layers in the set.
};
/// Represents a simple, non-overlapping contour in field space.
struct rcContour
{
inline rcContour() : verts(0), nverts(0), rverts(0), nrverts(0) { }
inline ~rcContour() { delete [] verts; delete [] rverts; }
int* verts; // Vertex coordinates, each vertex contains 4 components.
int nverts; // Number of vertices.
int* rverts; // Raw vertex coordinates, each vertex contains 4 components.
int nrverts; // Number of raw vertices.
unsigned short reg; // Region ID of the contour.
int* verts; ///< Simplified contour vertex and connection data. [Size: 4 * #nverts]
int nverts; ///< The number of vertices in the simplified contour.
int* rverts; ///< Raw contour vertex and connection data. [Size: 4 * #nrverts]
int nrverts; ///< The number of vertices in the raw contour.
unsigned short reg; ///< The region id of the contour.
unsigned char area; ///< The area id of the contour.
};
/// Represents a group of related contours.
/// @ingroup recast
struct rcContourSet
{
inline rcContourSet() : conts(0), nconts(0) {}
inline ~rcContourSet() { delete [] conts; }
rcContour* conts; // Pointer to all contours.
int nconts; // Number of contours.
float bmin[3], bmax[3]; // Bounding box of the heightfield.
float cs, ch; // Cell size and height.
rcContour* conts; ///< An array of the contours in the set. [Size: #nconts]
int nconts; ///< The number of contours in the set.
float bmin[3]; ///< The minimum bounds in world space. [(x, y, z)]
float bmax[3]; ///< The maximum bounds in world space. [(x, y, z)]
float cs; ///< The size of each cell. (On the xz-plane.)
float ch; ///< The height of each cell. (The minimum increment along the y-axis.)
int width; ///< The width of the set. (Along the x-axis in cell units.)
int height; ///< The height of the set. (Along the z-axis in cell units.)
int borderSize; ///< The AABB border size used to generate the source data from which the contours were derived.
};
// Polymesh store a connected mesh of polygons.
// The polygons are store in an array where each polygons takes
// 'nvp*2' elements. The first 'nvp' elements are indices to vertices
// and the second 'nvp' elements are indices to neighbour polygons.
// If a polygona has less than 'bvp' vertices, the remaining indices
// are set os 0xffff. If an polygon edge does not have a neighbour
// the neighbour index is set to 0xffff.
// Vertices can be transformed into world space as follows:
// x = bmin[0] + verts[i*3+0]*cs;
// y = bmin[1] + verts[i*3+1]*ch;
// z = bmin[2] + verts[i*3+2]*cs;
/// Represents a polygon mesh suitable for use in building a navigation mesh.
/// @ingroup recast
struct rcPolyMesh
{
inline rcPolyMesh() : verts(0), polys(0), regs(0), nverts(0), npolys(0), nvp(3) {}
inline ~rcPolyMesh() { delete [] verts; delete [] polys; delete [] regs; }
unsigned short* verts; // Vertices of the mesh, 3 elements per vertex.
unsigned short* polys; // Polygons of the mesh, nvp*2 elements per polygon.
unsigned short* regs; // Regions of the polygons.
int nverts; // Number of vertices.
int npolys; // Number of polygons.
int nvp; // Max number of vertices per polygon.
float bmin[3], bmax[3]; // Bounding box of the mesh.
float cs, ch; // Cell size and height.
{
unsigned short* verts; ///< The mesh vertices. [Form: (x, y, z) * #nverts]
unsigned short* polys; ///< Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp]
unsigned short* regs; ///< The region id assigned to each polygon. [Length: #maxpolys]
unsigned short* flags; ///< The user defined flags for each polygon. [Length: #maxpolys]
unsigned char* areas; ///< The area id assigned to each polygon. [Length: #maxpolys]
int nverts; ///< The number of vertices.
int npolys; ///< The number of polygons.
int maxpolys; ///< The number of allocated polygons.
int nvp; ///< The maximum number of vertices per polygon.
float bmin[3]; ///< The minimum bounds in world space. [(x, y, z)]
float bmax[3]; ///< The maximum bounds in world space. [(x, y, z)]
float cs; ///< The size of each cell. (On the xz-plane.)
float ch; ///< The height of each cell. (The minimum increment along the y-axis.)
int borderSize; ///< The AABB border size used to generate the source data from which the mesh was derived.
};
// Detail mesh generated from a rcPolyMesh.
// Each submesh represents a polygon in the polymesh and they are stored in
// excatly same order. Each submesh is described as 4 values:
// base vertex, vertex count, base triangle, triangle count. That is,
// const unsigned char* t = &dtl.tris[(tbase+i)*3]; and
// const float* v = &dtl.verts[(vbase+t[j])*3];
// If the input polygon has 'n' vertices, those vertices are first in the
// submesh vertex list. This allows to compres the mesh by not storing the
// first vertices and using the polymesh vertices instead.
/// Contains triangle meshes that represent detailed height data associated
/// with the polygons in its associated polygon mesh object.
/// @ingroup recast
struct rcPolyMeshDetail
{
inline rcPolyMeshDetail() :
meshes(0), verts(0), tris(0),
nmeshes(0), nverts(0), ntris(0) {}
inline ~rcPolyMeshDetail()
{
delete [] meshes; delete [] verts; delete [] tris;
}
unsigned short* meshes; // Pointer to all mesh data.
float* verts; // Pointer to all vertex data.
unsigned char* tris; // Pointer to all triangle data.
int nmeshes; // Number of meshes.
int nverts; // Number of total vertices.
int ntris; // Number of triangles.
unsigned int* meshes; ///< The sub-mesh data. [Size: 4*#nmeshes]
float* verts; ///< The mesh vertices. [Size: 3*#nverts]
unsigned char* tris; ///< The mesh triangles. [Size: 4*#ntris]
int nmeshes; ///< The number of sub-meshes defined by #meshes.
int nverts; ///< The number of vertices in #verts.
int ntris; ///< The number of triangles in #tris.
};
/// @name Allocation Functions
/// Functions used to allocate and de-allocate Recast objects.
/// @see rcAllocSetCustom
/// @{
// Simple dynamic array ints.
class rcIntArray
{
int* m_data;
int m_size, m_cap;
public:
inline rcIntArray() : m_data(0), m_size(0), m_cap(0) {}
inline rcIntArray(int n) : m_data(0), m_size(0), m_cap(n) { m_data = new int[n]; }
inline ~rcIntArray() { delete [] m_data; }
void resize(int n);
inline void push(int item) { resize(m_size+1); m_data[m_size-1] = item; }
inline int pop() { if (m_size > 0) m_size--; return m_data[m_size]; }
inline const int& operator[](int i) const { return m_data[i]; }
inline int& operator[](int i) { return m_data[i]; }
inline int size() const { return m_size; }
};
/// Allocates a heightfield object using the Recast allocator.
/// @return A heightfield that is ready for initialization, or null on failure.
/// @ingroup recast
/// @see rcCreateHeightfield, rcFreeHeightField
rcHeightfield* rcAllocHeightfield();
enum rcSpanFlags
{
RC_WALKABLE = 0x01,
RC_REACHABLE = 0x02,
};
/// Frees the specified heightfield object using the Recast allocator.
/// @param[in] hf A heightfield allocated using #rcAllocHeightfield
/// @ingroup recast
/// @see rcAllocHeightfield
void rcFreeHeightField(rcHeightfield* hf);
// If heightfield region ID has the following bit set, the region is on border area
// and excluded from many calculations.
/// Allocates a compact heightfield object using the Recast allocator.
/// @return A compact heightfield that is ready for initialization, or null on failure.
/// @ingroup recast
/// @see rcBuildCompactHeightfield, rcFreeCompactHeightfield
rcCompactHeightfield* rcAllocCompactHeightfield();
/// Frees the specified compact heightfield object using the Recast allocator.
/// @param[in] chf A compact heightfield allocated using #rcAllocCompactHeightfield
/// @ingroup recast
/// @see rcAllocCompactHeightfield
void rcFreeCompactHeightfield(rcCompactHeightfield* chf);
/// Allocates a heightfield layer set using the Recast allocator.
/// @return A heightfield layer set that is ready for initialization, or null on failure.
/// @ingroup recast
/// @see rcBuildHeightfieldLayers, rcFreeHeightfieldLayerSet
rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet();
/// Frees the specified heightfield layer set using the Recast allocator.
/// @param[in] lset A heightfield layer set allocated using #rcAllocHeightfieldLayerSet
/// @ingroup recast
/// @see rcAllocHeightfieldLayerSet
void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset);
/// Allocates a contour set object using the Recast allocator.
/// @return A contour set that is ready for initialization, or null on failure.
/// @ingroup recast
/// @see rcBuildContours, rcFreeContourSet
rcContourSet* rcAllocContourSet();
/// Frees the specified contour set using the Recast allocator.
/// @param[in] cset A contour set allocated using #rcAllocContourSet
/// @ingroup recast
/// @see rcAllocContourSet
void rcFreeContourSet(rcContourSet* cset);
/// Allocates a polygon mesh object using the Recast allocator.
/// @return A polygon mesh that is ready for initialization, or null on failure.
/// @ingroup recast
/// @see rcBuildPolyMesh, rcFreePolyMesh
rcPolyMesh* rcAllocPolyMesh();
/// Frees the specified polygon mesh using the Recast allocator.
/// @param[in] pmesh A polygon mesh allocated using #rcAllocPolyMesh
/// @ingroup recast
/// @see rcAllocPolyMesh
void rcFreePolyMesh(rcPolyMesh* pmesh);
/// Allocates a detail mesh object using the Recast allocator.
/// @return A detail mesh that is ready for initialization, or null on failure.
/// @ingroup recast
/// @see rcBuildPolyMeshDetail, rcFreePolyMeshDetail
rcPolyMeshDetail* rcAllocPolyMeshDetail();
/// Frees the specified detail mesh using the Recast allocator.
/// @param[in] dmesh A detail mesh allocated using #rcAllocPolyMeshDetail
/// @ingroup recast
/// @see rcAllocPolyMeshDetail
void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh);
/// @}
/// Heighfield border flag.
/// If a heightfield region ID has this bit set, then the region is a border
/// region and its spans are considered unwalkable.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg
static const unsigned short RC_BORDER_REG = 0x8000;
// If contour region ID has the following bit set, the vertex will be later
// removed in order to match the segments and vertices at tile boundaries.
/// Border vertex flag.
/// If a region ID has this bit set, then the associated element lies on
/// a tile border. If a contour vertex's region ID has this bit set, the
/// vertex will later be removed in order to match the segments and vertices
/// at tile boundaries.
/// (Used during the build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
static const int RC_BORDER_VERTEX = 0x10000;
// Compact span neighbour helpers.
inline int rcGetCon(const rcCompactSpan& s, int dir)
{
return (s.con >> (dir*4)) & 0xf;
}
/// Area border flag.
/// If a region ID has this bit set, then the associated element lies on
/// the border of an area.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
static const int RC_AREA_BORDER = 0x20000;
inline int rcGetDirOffsetX(int dir)
/// Contour build flags.
/// @see rcBuildContours
enum rcBuildContoursFlags
{
const int offset[4] = { -1, 0, 1, 0, };
return offset[dir&0x03];
}
RC_CONTOUR_TESS_WALL_EDGES = 0x01, ///< Tessellate solid (impassable) edges during contour simplification.
RC_CONTOUR_TESS_AREA_EDGES = 0x02, ///< Tessellate edges between areas during contour simplification.
};
inline int rcGetDirOffsetY(int dir)
{
const int offset[4] = { 0, 1, 0, -1 };
return offset[dir&0x03];
}
/// Applied to the region id field of contour vertices in order to extract the region id.
/// The region id field of a vertex may have several flags applied to it. So the
/// fields value can't be used directly.
/// @see rcContour::verts, rcContour::rverts
static const int RC_CONTOUR_REG_MASK = 0xffff;
// Common helper functions
/// An value which indicates an invalid index within a mesh.
/// @note This does not necessarily indicate an error.
/// @see rcPolyMesh::polys
static const unsigned short RC_MESH_NULL_IDX = 0xffff;
/// Represents the null area.
/// When a data element is given this value it is considered to no longer be
/// assigned to a usable area. (E.g. It is unwalkable.)
static const unsigned char RC_NULL_AREA = 0;
/// The default area id used to indicate a walkable polygon.
/// This is also the maximum allowed area id, and the only non-null area id
/// recognized by some steps in the build process.
static const unsigned char RC_WALKABLE_AREA = 63;
/// The value returned by #rcGetCon if the specified direction is not connected
/// to another span. (Has no neighbor.)
static const int RC_NOT_CONNECTED = 0x3f;
/// @name General helper functions
/// @{
/// Swaps the values of the two parameters.
/// @param[in,out] a Value A
/// @param[in,out] b Value B
template<class T> inline void rcSwap(T& a, T& b) { T t = a; a = b; b = t; }
/// Returns the minimum of two values.
/// @param[in] a Value A
/// @param[in] b Value B
/// @return The minimum of the two values.
template<class T> inline T rcMin(T a, T b) { return a < b ? a : b; }
/// Returns the maximum of two values.
/// @param[in] a Value A
/// @param[in] b Value B
/// @return The maximum of the two values.
template<class T> inline T rcMax(T a, T b) { return a > b ? a : b; }
/// Returns the absolute value.
/// @param[in] a The value.
/// @return The absolute value of the specified value.
template<class T> inline T rcAbs(T a) { return a < 0 ? -a : a; }
/// Return the square of a value.
/// @param[in] a The value.
/// @return The square of the value.
template<class T> inline T rcSqr(T a) { return a*a; }
/// Clamps the value to the specified range.
/// @param[in] v The value to clamp.
/// @param[in] mn The minimum permitted return value.
/// @param[in] mx The maximum permitted return value.
/// @return The value, clamped to the specified range.
template<class T> inline T rcClamp(T v, T mn, T mx) { return v < mn ? mn : (v > mx ? mx : v); }
// Common vector helper functions.
inline void vcross(float* dest, const float* v1, const float* v2)
/// Returns the square root of the value.
/// @param[in] x The value.
/// @return The square root of the vlaue.
float rcSqrt(float x);
/// Not documented. Internal use only.
/// @param[in] x Not documented.
/// @return Not documented.
inline int rcAlign4(int x) { return (x+3) & ~3; }
/// @}
/// @name Vector helper functions.
/// @{
/// Derives the cross product of two vectors. (v1 x v2)
/// @param[out] dest The cross product. [(x, y, z)]
/// @param[in] v1 A Vector [(x, y, z)]
/// @param[in] v2 A vector [(x, y, z)]
inline void rcVcross(float* dest, const float* v1, const float* v2)
{
dest[0] = v1[1]*v2[2] - v1[2]*v2[1];
dest[1] = v1[2]*v2[0] - v1[0]*v2[2];
dest[2] = v1[0]*v2[1] - v1[1]*v2[0];
dest[2] = v1[0]*v2[1] - v1[1]*v2[0];
}
inline float vdot(const float* v1, const float* v2)
/// Derives the dot product of two vectors. (v1 . v2)
/// @param[in] v1 A Vector [(x, y, z)]
/// @param[in] v2 A vector [(x, y, z)]
/// @return The dot product.
inline float rcVdot(const float* v1, const float* v2)
{
return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
}
inline void vmad(float* dest, const float* v1, const float* v2, const float s)
/// Performs a scaled vector addition. (v1 + (v2 * s))
/// @param[out] dest The result vector. [(x, y, z)]
/// @param[in] v1 The base vector [(x, y, z)]
/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)]
/// @param[in] s The amount to scale @p v2 by before adding to @p v1.
inline void rcVmad(float* dest, const float* v1, const float* v2, const float s)
{
dest[0] = v1[0]+v2[0]*s;
dest[1] = v1[1]+v2[1]*s;
dest[2] = v1[2]+v2[2]*s;
}
inline void vadd(float* dest, const float* v1, const float* v2)
/// Performs a vector addition. (@p v1 + @p v2)
/// @param[out] dest The result vector. [(x, y, z)]
/// @param[in] v1 The base vector [(x, y, z)]
/// @param[in] v2 The vector to add to @p v1. [(x, y, z)]
inline void rcVadd(float* dest, const float* v1, const float* v2)
{
dest[0] = v1[0]+v2[0];
dest[1] = v1[1]+v2[1];
dest[2] = v1[2]+v2[2];
}
inline void vsub(float* dest, const float* v1, const float* v2)
/// Performs a vector subtraction. (@p v1 - @p v2)
/// @param[out] dest The result vector. [(x, y, z)]
/// @param[in] v1 The base vector [(x, y, z)]
/// @param[in] v2 The vector to subtract from @p v1. [(x, y, z)]
inline void rcVsub(float* dest, const float* v1, const float* v2)
{
dest[0] = v1[0]-v2[0];
dest[1] = v1[1]-v2[1];
dest[2] = v1[2]-v2[2];
}
inline void vmin(float* mn, const float* v)
/// Selects the minimum value of each element from the specified vectors.
/// @param[in, out] mn A vector. (Will be updated with the result.) [(x, y, z)]
/// @param[in] v A vector. [(x, y, z)]
inline void rcVmin(float* mn, const float* v)
{
mn[0] = rcMin(mn[0], v[0]);
mn[1] = rcMin(mn[1], v[1]);
mn[2] = rcMin(mn[2], v[2]);
}
inline void vmax(float* mx, const float* v)
/// Selects the maximum value of each element from the specified vectors.
/// @param[in, out] mx A vector. (Will be updated with the result.) [(x, y, z)]
/// @param[in] v A vector. [(x, y, z)]
inline void rcVmax(float* mx, const float* v)
{
mx[0] = rcMax(mx[0], v[0]);
mx[1] = rcMax(mx[1], v[1]);
mx[2] = rcMax(mx[2], v[2]);
}
inline void vcopy(float* dest, const float* v)
/// Performs a vector copy.
/// @param[out] dest The result. [(x, y, z)]
/// @param[in] v The vector to copy [(x, y, z)]
inline void rcVcopy(float* dest, const float* v)
{
dest[0] = v[0];
dest[1] = v[1];
dest[2] = v[2];
}
inline float vdist(const float* v1, const float* v2)
/// Returns the distance between two points.
/// @param[in] v1 A point. [(x, y, z)]
/// @param[in] v2 A point. [(x, y, z)]
/// @return The distance between the two points.
inline float rcVdist(const float* v1, const float* v2)
{
float dx = v2[0] - v1[0];
float dy = v2[1] - v1[1];
float dz = v2[2] - v1[2];
return sqrtf(dx*dx + dy*dy + dz*dz);
return rcSqrt(dx*dx + dy*dy + dz*dz);
}
inline float vdistSqr(const float* v1, const float* v2)
/// Returns the square of the distance between two points.
/// @param[in] v1 A point. [(x, y, z)]
/// @param[in] v2 A point. [(x, y, z)]
/// @return The square of the distance between the two points.
inline float rcVdistSqr(const float* v1, const float* v2)
{
float dx = v2[0] - v1[0];
float dy = v2[1] - v1[1];
@@ -318,184 +705,424 @@ inline float vdistSqr(const float* v1, const float* v2)
return dx*dx + dy*dy + dz*dz;
}
inline void vnormalize(float* v)
/// Normalizes the vector.
/// @param[in,out] v The vector to normalize. [(x, y, z)]
inline void rcVnormalize(float* v)
{
float d = 1.0f / sqrtf(rcSqr(v[0]) + rcSqr(v[1]) + rcSqr(v[2]));
float d = 1.0f / rcSqrt(rcSqr(v[0]) + rcSqr(v[1]) + rcSqr(v[2]));
v[0] *= d;
v[1] *= d;
v[2] *= d;
}
inline bool vequal(const float* p0, const float* p1)
/// Not documented. Internal use only.
/// @param[in] p0 Not documented.
/// @param[in] p1 Not documented.
/// @return Not documented.
inline bool rcVequal(const float* p0, const float* p1)
{
static const float thr = rcSqr(1.0f/16384.0f);
const float d = vdistSqr(p0, p1);
const float d = rcVdistSqr(p0, p1);
return d < thr;
}
/// @}
/// @name Heightfield Functions
/// @see rcHeightfield
/// @{
// Calculated bounding box of array of vertices.
// Params:
// verts - (in) array of vertices
// nv - (in) vertex count
// bmin, bmax - (out) bounding box
/// Calculates the bounding box of an array of vertices.
/// @ingroup recast
/// @param[in] verts An array of vertices. [(x, y, z) * @p nv]
/// @param[in] nv The number of vertices in the @p verts array.
/// @param[out] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu]
/// @param[out] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu]
void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax);
// Calculates grid size based on bounding box and grid cell size.
// Params:
// bmin, bmax - (in) bounding box
// cs - (in) grid cell size
// w - (out) grid width
// h - (out) grid height
/// Calculates the grid size based on the bounding box and grid cell size.
/// @ingroup recast
/// @param[in] bmin The minimum bounds of the AABB. [(x, y, z)] [Units: wu]
/// @param[in] bmax The maximum bounds of the AABB. [(x, y, z)] [Units: wu]
/// @param[in] cs The xz-plane cell size. [Limit: > 0] [Units: wu]
/// @param[out] w The width along the x-axis. [Limit: >= 0] [Units: vx]
/// @param[out] h The height along the z-axis. [Limit: >= 0] [Units: vx]
void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int* h);
// Creates and initializes new heightfield.
// Params:
// hf - (in/out) heightfield to initialize.
// width - (in) width of the heightfield.
// height - (in) height of the heightfield.
// bmin, bmax - (in) bounding box of the heightfield
// cs - (in) grid cell size
// ch - (in) grid cell height
bool rcCreateHeightfield(rcHeightfield& hf, int width, int height,
/// Initializes a new heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in,out] hf The allocated heightfield to initialize.
/// @param[in] width The width of the field along the x-axis. [Limit: >= 0] [Units: vx]
/// @param[in] height The height of the field along the z-axis. [Limit: >= 0] [Units: vx]
/// @param[in] bmin The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu]
/// @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]
bool rcCreateHeightfield(rcContext* ctx, rcHeightfield& hf, int width, int height,
const float* bmin, const float* bmax,
float cs, float ch);
// Sets the WALKABLE flag for every triangle whose slope is below
// the maximun walkable slope angle.
// Params:
// walkableSlopeAngle - (in) maximun slope angle in degrees.
// verts - (in) array of vertices
// nv - (in) vertex count
// tris - (in) array of triangle vertex indices
// nt - (in) triangle count
// flags - (out) array of triangle flags
void rcMarkWalkableTriangles(const float walkableSlopeAngle,
const float* verts, int nv,
const int* tris, int nt,
unsigned char* flags);
/// Sets the area id of all triangles with a slope below the specified value
/// to #RC_WALKABLE_AREA.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. [Limits: 0 <= value < 90]
/// [Units: Degrees]
/// @param[in] verts The vertices. [(x, y, z) * @p nv]
/// @param[in] nv The number of vertices.
/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt]
/// @param[in] nt The number of triangles.
/// @param[out] areas The triangle area ids. [Length: >= @p nt]
void rcMarkWalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv,
const int* tris, int nt, unsigned char* areas);
// Rasterizes a triangle into heightfield spans.
// Params:
// v0,v1,v2 - (in) the vertices of the triangle.
// flags - (in) triangle flags (uses WALKABLE)
// solid - (in) heighfield where the triangle is rasterized
void rcRasterizeTriangle(const float* v0, const float* v1, const float* v2,
unsigned char flags, rcHeightfield& solid);
/// Sets the area id of all triangles with a slope greater than or equal to the specified value to #RC_NULL_AREA.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] walkableSlopeAngle The maximum slope that is considered walkable. [Limits: 0 <= value < 90]
/// [Units: Degrees]
/// @param[in] verts The vertices. [(x, y, z) * @p nv]
/// @param[in] nv The number of vertices.
/// @param[in] tris The triangle vertex indices. [(vertA, vertB, vertC) * @p nt]
/// @param[in] nt The number of triangles.
/// @param[out] areas The triangle area ids. [Length: >= @p nt]
void rcClearUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, const float* verts, int nv,
const int* tris, int nt, unsigned char* areas);
// Rasterizes the triangles into heightfield spans.
// Params:
// verts - (in) array of vertices
// nv - (in) vertex count
// tris - (in) array of triangle vertex indices
// norms - (in) array of triangle normals
// flags - (in) array of triangle flags (uses WALKABLE)
// nt - (in) triangle count
// solid - (in) heighfield where the triangles are rasterized
void rcRasterizeTriangles(const float* verts, int nv,
const int* tris, const unsigned char* flags, int nt,
rcHeightfield& solid);
/// Adds a span to the specified heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in,out] hf An initialized heightfield.
/// @param[in] x The width index where the span is to be added.
/// [Limits: 0 <= value < rcHeightfield::width]
/// @param[in] y The height index where the span is to be added.
/// [Limits: 0 <= value < rcHeightfield::height]
/// @param[in] smin The minimum height of the span. [Limit: < @p smax] [Units: vx]
/// @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,
const unsigned short smin, const unsigned short smax,
const unsigned char area, const int flagMergeThr);
// Removes WALKABLE flag from all spans that are at ledges. This filtering
// removes possible overestimation of the conservative voxelization so that
// the resulting mesh will not have regions hanging in air over ledges.
// Params:
// walkableHeight - (in) minimum height where the agent can still walk
// walkableClimb - (in) maximum height between grid cells the agent can climb
// solid - (in/out) heightfield describing the solid space
void rcFilterLedgeSpans(const int walkableHeight,
const int walkableClimb,
rcHeightfield& solid);
/// Rasterizes a triangle into the specified heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] v0 Triangle vertex 0 [(x, y, z)]
/// @param[in] v1 Triangle vertex 1 [(x, y, z)]
/// @param[in] v2 Triangle vertex 2 [(x, y, z)]
/// @param[in] area The area id of the triangle. [Limit: <= #RC_WALKABLE_AREA]
/// @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,
const unsigned char area, rcHeightfield& solid,
const int flagMergeThr = 1);
// Removes WALKABLE flag from all spans which have smaller than
// 'walkableHeight' clearane above them.
// Params:
// walkableHeight - (in) minimum height where the agent can still walk
// solid - (in/out) heightfield describing the solid space
void rcFilterWalkableLowHeightSpans(int walkableHeight,
rcHeightfield& solid);
/// Rasterizes an indexed triangle mesh into the specified heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] verts The vertices. [(x, y, z) * @p nv]
/// @param[in] nv The number of vertices.
/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt]
/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt]
/// @param[in] nt The number of triangles.
/// @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,
const int* tris, const unsigned char* areas, const int nt,
rcHeightfield& solid, const int flagMergeThr = 1);
// Marks spans which are reachable from any of the topmost spans.
// Params:
// walkableHeight - (in) minimum height where the agent can still walk
// walkableClimb - (in) maximum height between grid cells the agent can climb
// solid - (in/out) heightfield describing the solid space
// Returns false if operation ran out of memory.
bool rcMarkReachableSpans(const int walkableHeight,
const int walkableClimb,
rcHeightfield& solid);
/// Rasterizes an indexed triangle mesh into the specified heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] verts The vertices. [(x, y, z) * @p nv]
/// @param[in] nv The number of vertices.
/// @param[in] tris The triangle indices. [(vertA, vertB, vertC) * @p nt]
/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt]
/// @param[in] nt The number of triangles.
/// @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,
const unsigned short* tris, const unsigned char* areas, const int nt,
rcHeightfield& solid, const int flagMergeThr = 1);
// Builds compact representation of the heightfield.
// Params:
// walkableHeight - (in) minimum height where the agent can still walk
// walkableClimb - (in) maximum height between grid cells the agent can climb
// hf - (in) heightfield to be compacted
// chf - (out) compact heightfield representing the open space.
// Returns false if operation ran out of memory.
bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb,
unsigned char flags,
rcHeightfield& hf,
rcCompactHeightfield& chf);
/// Rasterizes triangles into the specified heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] verts The triangle vertices. [(ax, ay, az, bx, by, bz, cx, by, cx) * @p nt]
/// @param[in] areas The area id's of the triangles. [Limit: <= #RC_WALKABLE_AREA] [Size: @p nt]
/// @param[in] nt The number of triangles.
/// @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,
rcHeightfield& solid, const int flagMergeThr = 1);
// Builds distance field and stores it into the combat heightfield.
// Params:
// chf - (in/out) compact heightfield representing the open space.
// Returns false if operation ran out of memory.
bool rcBuildDistanceField(rcCompactHeightfield& chf);
/// Marks non-walkable spans as walkable if their maximum is within @p walkableClimp of a walkable neihbor.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable.
/// [Limit: >=0] [Units: vx]
/// @param[in,out] solid A fully built heightfield. (All spans have been added.)
void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid);
// Divides the walkable heighfied into simple regions.
// Each region has only one contour and no overlaps.
// The regions are stored in the compact heightfield 'reg' field.
// The regions will be shrinked by the radius of the agent.
// The process sometimes creates small regions. The parameter
// 'minRegionSize' specifies the smallest allowed regions size.
// If the area of a regions is smaller than allowed, the regions is
// removed or merged to neighbour region.
// Params:
// chf - (in/out) compact heightfield representing the open space.
// walkableRadius - (in) the radius of the agent.
// minRegionSize - (in) the smallest allowed regions size.
// maxMergeRegionSize - (in) the largest allowed regions size which can be merged.
// Returns false if operation ran out of memory.
bool rcBuildRegions(rcCompactHeightfield& chf,
int walkableRadius, int borderSize,
int minRegionSize, int mergeRegionSize);
/// Marks spans that are ledges as not-walkable.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to
/// be considered walkable. [Limit: >= 3] [Units: vx]
/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable.
/// [Limit: >=0] [Units: vx]
/// @param[in,out] solid A fully built heightfield. (All spans have been added.)
void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight,
const int walkableClimb, rcHeightfield& solid);
// Builds simplified contours from the regions outlines.
// Params:
// chf - (in) compact heightfield which has regions set.
// maxError - (in) maximum allowed distance between simplified countour and cells.
// maxEdgeLen - (in) maximum allowed contour edge length in cells.
// cset - (out) Resulting contour set.
// Returns false if operation ran out of memory.
bool rcBuildContours(rcCompactHeightfield& chf,
/// Marks walkable spans as not walkable if the clearence above the span is less than the specified height.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area to
/// be considered walkable. [Limit: >= 3] [Units: vx]
/// @param[in,out] solid A fully built heightfield. (All spans have been added.)
void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid);
/// Returns the number of spans contained in the specified heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] hf An initialized heightfield.
/// @returns The number of spans in the heightfield.
int rcGetHeightFieldSpanCount(rcContext* ctx, rcHeightfield& hf);
/// @}
/// @name Compact Heightfield Functions
/// @see rcCompactHeightfield
/// @{
/// Builds a compact heightfield representing open space, from a heightfield representing solid space.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area
/// to be considered walkable. [Limit: >= 3] [Units: vx]
/// @param[in] walkableClimb Maximum ledge height that is considered to still be traversable.
/// [Limit: >=0] [Units: vx]
/// @param[in] hf The heightfield to be compacted.
/// @param[out] chf The resulting compact heightfield. (Must be pre-allocated.)
/// @returns True if the operation completed successfully.
bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb,
rcHeightfield& hf, rcCompactHeightfield& chf);
/// Erodes the walkable area within the heightfield by the specified radius.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] radius The radius of erosion. [Limits: 0 < value < 255] [Units: vx]
/// @param[in,out] chf The populated compact heightfield to erode.
/// @returns True if the operation completed successfully.
bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf);
/// Applies a median filter to walkable area types (based on area id), removing noise.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in,out] chf A populated compact heightfield.
/// @returns True if the operation completed successfully.
bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf);
/// Applies an area id to all spans within the specified bounding box. (AABB)
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] bmin The minimum of the bounding box. [(x, y, z)]
/// @param[in] bmax The maximum of the bounding box. [(x, y, z)]
/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] chf A populated compact heightfield.
void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigned char areaId,
rcCompactHeightfield& chf);
/// Applies the area id to the all spans within the specified convex polygon.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] verts The vertices of the polygon [Fomr: (x, y, z) * @p nverts]
/// @param[in] nverts The number of vertices in the polygon.
/// @param[in] hmin The height of the base of the polygon.
/// @param[in] hmax The height of the top of the polygon.
/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] chf A populated compact heightfield.
void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts,
const float hmin, const float hmax, unsigned char areaId,
rcCompactHeightfield& chf);
/// Applies the area id to all spans within the specified cylinder.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] pos The center of the base of the cylinder. [Form: (x, y, z)]
/// @param[in] r The radius of the cylinder.
/// @param[in] h The height of the cylinder.
/// @param[in] areaId The area id to apply. [Limit: <= #RC_WALKABLE_AREA]
/// @param[in,out] chf A populated compact heightfield.
void rcMarkCylinderArea(rcContext* ctx, const float* pos,
const float r, const float h, unsigned char areaId,
rcCompactHeightfield& chf);
/// Builds the distance field for the specified compact heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in,out] chf A populated compact heightfield.
/// @returns True if the operation completed successfully.
bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf);
/// 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.
/// @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].
/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible,
/// be merged with larger regions. [Limit: >=0] [Units: vx]
/// @returns True if the operation completed successfully.
bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf,
const int borderSize, const int minRegionArea, const int mergeRegionArea);
/// Builds region data for the heightfield using simple monotone partitioning.
/// @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].
/// @param[in] mergeRegionArea Any regions with a span count smaller than this value will, if possible,
/// be merged with larger regions. [Limit: >=0] [Units: vx]
/// @returns True if the operation completed successfully.
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]
/// @param[in] i The index of the neighbor span.
inline void rcSetCon(rcCompactSpan& s, int dir, int i)
{
const unsigned int shift = (unsigned int)dir*6;
unsigned int con = s.con;
s.con = (con & ~(0x3f << shift)) | (((unsigned int)i & 0x3f) << shift);
}
/// Gets neighbor connection data for the specified direction.
/// @param[in] s The span to check.
/// @param[in] dir The direction to check. [Limits: 0 <= value < 4]
/// @return The neighbor connection data for the specified direction,
/// or #RC_NOT_CONNECTED if there is no connection.
inline int rcGetCon(const rcCompactSpan& s, int dir)
{
const unsigned int shift = (unsigned int)dir*6;
return (s.con >> shift) & 0x3f;
}
/// Gets the standard width (x-axis) offset for the specified direction.
/// @param[in] dir The direction. [Limits: 0 <= value < 4]
/// @return The width offset to apply to the current cell position to move
/// in the direction.
inline int rcGetDirOffsetX(int dir)
{
const int offset[4] = { -1, 0, 1, 0, };
return offset[dir&0x03];
}
/// Gets the standard height (z-axis) offset for the specified direction.
/// @param[in] dir The direction. [Limits: 0 <= value < 4]
/// @return The height offset to apply to the current cell position to move
/// in the direction.
inline int rcGetDirOffsetY(int dir)
{
const int offset[4] = { 0, 1, 0, -1 };
return offset[dir&0x03];
}
/// @}
/// @name Layer, Contour, Polymesh, and Detail Mesh Functions
/// @see rcHeightfieldLayer, rcContourSet, rcPolyMesh, rcPolyMeshDetail
/// @{
/// Builds a layer set from the specified compact heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] chf A fully built compact heightfield.
/// @param[in] borderSize The size of the non-navigable border around the heightfield. [Limit: >=0]
/// [Units: vx]
/// @param[in] walkableHeight Minimum floor to 'ceiling' height that will still allow the floor area
/// to be considered walkable. [Limit: >= 3] [Units: vx]
/// @param[out] lset The resulting layer set. (Must be pre-allocated.)
/// @returns True if the operation completed successfully.
bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf,
const int borderSize, const int walkableHeight,
rcHeightfieldLayerSet& lset);
/// Builds a contour set from the region outlines in the provided compact heightfield.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] chf A fully built compact heightfield.
/// @param[in] maxError The maximum distance a simplfied contour's border edges should deviate
/// the original raw contour. [Limit: >=0] [Units: wu]
/// @param[in] maxEdgeLen The maximum allowed length for contour edges along the border of the mesh.
/// [Limit: >=0] [Units: vx]
/// @param[out] cset The resulting contour set. (Must be pre-allocated.)
/// @param[in] buildFlags The build flags. (See: #rcBuildContoursFlags)
/// @returns True if the operation completed successfully.
bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf,
const float maxError, const int maxEdgeLen,
rcContourSet& cset);
rcContourSet& cset, const int flags = RC_CONTOUR_TESS_WALL_EDGES);
// Builds connected convex polygon mesh from contour polygons.
// Params:
// cset - (in) contour set.
// nvp - (in) maximum number of vertices per polygon.
// mesh - (out) poly mesh.
// Returns false if operation ran out of memory.
bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh);
/// Builds a polygon mesh from the provided contours.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] cset A fully built contour set.
/// @param[in] nvp The maximum number of vertices allowed for polygons generated during the
/// contour to polygon conversion process. [Limit: >= 3]
/// @param[out] mesh The resulting polygon mesh. (Must be re-allocated.)
/// @returns True if the operation completed successfully.
bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh);
bool rcMergePolyMeshes(rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh);
/// Merges multiple polygon meshes into a single mesh.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] meshes An array of polygon meshes to merge. [Size: @p nmeshes]
/// @param[in] nmeshes The number of polygon meshes in the meshes array.
/// @param[in] mesh The resulting polygon mesh. (Must be pre-allocated.)
/// @returns True if the operation completed successfully.
bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh);
// Builds detail triangle mesh for each polygon in the poly mesh.
// Params:
// mesh - (in) poly mesh to detail.
// chf - (in) compacy height field, used to query height for new vertices.
// sampleDist - (in) spacing between height samples used to generate more detail into mesh.
// sampleMaxError - (in) maximum allowed distance between simplified detail mesh and height sample.
// pmdtl - (out) detail mesh.
// Returns false if operation ran out of memory.
bool rcBuildPolyMeshDetail(const rcPolyMesh& mesh, const rcCompactHeightfield& chf,
/// Builds a detail mesh from the provided polygon mesh.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] mesh A fully built polygon mesh.
/// @param[in] chf The compact heightfield used to build the polygon mesh.
/// @param[in] sampleDist Sets the distance to use when samping the heightfield. [Limit: >=0] [Units: wu]
/// @param[in] sampleMaxError The maximum distance the detail mesh surface should deviate from
/// heightfield data. [Limit: >=0] [Units: wu]
/// @param[out] dmesh The resulting detail mesh. (Must be pre-allocated.)
/// @returns True if the operation completed successfully.
bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf,
const float sampleDist, const float sampleMaxError,
rcPolyMeshDetail& dmesh);
bool rcMergePolyMeshDetails(rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh);
/// Merges multiple detail meshes into a single detail mesh.
/// @ingroup recast
/// @param[in,out] ctx The build context to use during the operation.
/// @param[in] meshes An array of detail meshes to merge. [Size: @p nmeshes]
/// @param[in] nmeshes The number of detail meshes in the meshes array.
/// @param[out] mesh The resulting detail mesh. (Must be pre-allocated.)
/// @returns True if the operation completed successfully.
bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh);
bool buildMeshAdjacency(unsigned short* polys, const int npolys, const int nverts, const int vertsPerPoly);
/// @}
#endif // RECAST_H
///////////////////////////////////////////////////////////////////////////
// Due to the large amount of detail documentation for this file,
// the content normally located at the end of the header file has been separated
// out to a file in /Docs/Extern.

View File

@@ -0,0 +1,122 @@
//
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//
#ifndef RECASTALLOC_H
#define RECASTALLOC_H
/// Provides hint values to the memory allocator on how long the
/// memory is expected to be used.
enum rcAllocHint
{
RC_ALLOC_PERM, ///< Memory will persist after a function call.
RC_ALLOC_TEMP ///< Memory used temporarily within a function.
};
/// A memory allocation function.
// @param[in] size The size, in bytes of memory, to allocate.
// @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);
/// A memory deallocation function.
/// @see rcAllocSetCustom
// @param[in] ptr
typedef void (rcFreeFunc)(void* ptr);
/// Sets the base custom allocation functions to be used by Recast.
/// @param[in] allocFunc The memory allocation function to be used by #rcAlloc
/// @param[in] freeFunc The memory de-allocation function to be used by #rcFree
void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc);
/// Allocates a memory block.
/// @param[in] size The size, in bytes of memory, to allocate.
/// @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.
void* rcAlloc(int size, rcAllocHint hint);
/// Deallocates a memory block.
/// @param[in] ptr A pointer to a memory block previously allocated using #rcAlloc.
void rcFree(void* ptr);
/// A simple dynamic array of integers.
class rcIntArray
{
int* m_data;
int m_size, m_cap;
inline rcIntArray(const rcIntArray&);
inline 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) {}
/// 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); }
/// Specifies the new size of the integer array.
/// @param[in] n The new size of the integer array.
void resize(int 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; }
/// 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]; }
/// 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]; }
/// 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]; }
/// The current size of the integer array.
inline int size() const { return m_size; }
};
/// A simple helper class used to delete an array when it goes out of scope.
/// @note This class is rarely if ever used by the end user.
template<class T> class rcScopedDelete
{
T* ptr;
inline T* operator=(T* p);
public:
/// Constructs an instance with a null pointer.
inline rcScopedDelete() : ptr(0) {}
/// Constructs an instance with the specified pointer.
/// @param[in] p An pointer to an allocated array.
inline rcScopedDelete(T* p) : ptr(p) {}
inline ~rcScopedDelete() { rcFree(ptr); }
/// The root array pointer.
/// @return The root array pointer.
inline operator T*() { return ptr; }
};
#endif

View File

@@ -0,0 +1,33 @@
//
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//
#ifndef RECASTASSERT_H
#define RECASTASSERT_H
// Note: This header file's only purpose is to include define assert.
// Feel free to change the file and include your own implementation instead.
#ifdef NDEBUG
// From http://cnicholson.net/2009/02/stupid-c-tricks-adventures-in-assert/
# define rcAssert(x) do { (void)sizeof(x); } while(__LINE__==-1,false)
#else
# include <assert.h>
# define rcAssert assert
#endif
#endif // RECASTASSERT_H

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2009 Mikko Mononen memon@inside.org
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
@@ -22,35 +22,178 @@
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include "Recast.h"
#include "RecastLog.h"
#include "RecastTimer.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"
void rcIntArray::resize(int n)
float rcSqrt(float x)
{
if (n > m_cap)
return sqrtf(x);
}
/// @class rcContext
/// @par
///
/// This class does not provide logging or timer functionality on its
/// own. Both must be provided by a concrete implementation
/// by overriding the protected member functions. Also, this class does not
/// provide an interface for extracting log messages. (Only adding them.)
/// So concrete implementations must provide one.
///
/// If no logging or timers are required, just pass an instance of this
/// class through the Recast build process.
///
/// @par
///
/// Example:
/// @code
/// // Where ctx is an instance of rcContext and filepath is a char array.
/// ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not load '%s'", filepath);
/// @endcode
void rcContext::log(const rcLogCategory category, const char* format, ...)
{
if (!m_logEnabled)
return;
static const int MSG_SIZE = 512;
char msg[MSG_SIZE];
va_list ap;
va_start(ap, format);
int len = vsnprintf(msg, MSG_SIZE, format, ap);
if (len >= MSG_SIZE)
{
if (!m_cap) m_cap = 8;
while (m_cap < n) m_cap *= 2;
int* newData = new int[m_cap];
if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int));
delete [] m_data;
m_data = newData;
len = MSG_SIZE-1;
msg[MSG_SIZE-1] = '\0';
}
m_size = n;
va_end(ap);
doLog(category, msg, len);
}
rcHeightfield* rcAllocHeightfield()
{
rcHeightfield* hf = (rcHeightfield*)rcAlloc(sizeof(rcHeightfield), RC_ALLOC_PERM);
memset(hf, 0, sizeof(rcHeightfield));
return hf;
}
void rcFreeHeightField(rcHeightfield* hf)
{
if (!hf) return;
// Delete span array.
rcFree(hf->spans);
// Delete span pools.
while (hf->pools)
{
rcSpanPool* next = hf->pools->next;
rcFree(hf->pools);
hf->pools = next;
}
rcFree(hf);
}
rcCompactHeightfield* rcAllocCompactHeightfield()
{
rcCompactHeightfield* chf = (rcCompactHeightfield*)rcAlloc(sizeof(rcCompactHeightfield), RC_ALLOC_PERM);
memset(chf, 0, sizeof(rcCompactHeightfield));
return chf;
}
void rcFreeCompactHeightfield(rcCompactHeightfield* chf)
{
if (!chf) return;
rcFree(chf->cells);
rcFree(chf->spans);
rcFree(chf->dist);
rcFree(chf->areas);
rcFree(chf);
}
rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet()
{
rcHeightfieldLayerSet* lset = (rcHeightfieldLayerSet*)rcAlloc(sizeof(rcHeightfieldLayerSet), RC_ALLOC_PERM);
memset(lset, 0, sizeof(rcHeightfieldLayerSet));
return lset;
}
void rcFreeHeightfieldLayerSet(rcHeightfieldLayerSet* lset)
{
if (!lset) return;
for (int i = 0; i < lset->nlayers; ++i)
{
rcFree(lset->layers[i].heights);
rcFree(lset->layers[i].areas);
rcFree(lset->layers[i].cons);
}
rcFree(lset->layers);
rcFree(lset);
}
rcContourSet* rcAllocContourSet()
{
rcContourSet* cset = (rcContourSet*)rcAlloc(sizeof(rcContourSet), RC_ALLOC_PERM);
memset(cset, 0, sizeof(rcContourSet));
return cset;
}
void rcFreeContourSet(rcContourSet* cset)
{
if (!cset) return;
for (int i = 0; i < cset->nconts; ++i)
{
rcFree(cset->conts[i].verts);
rcFree(cset->conts[i].rverts);
}
rcFree(cset->conts);
rcFree(cset);
}
rcPolyMesh* rcAllocPolyMesh()
{
rcPolyMesh* pmesh = (rcPolyMesh*)rcAlloc(sizeof(rcPolyMesh), RC_ALLOC_PERM);
memset(pmesh, 0, sizeof(rcPolyMesh));
return pmesh;
}
void rcFreePolyMesh(rcPolyMesh* pmesh)
{
if (!pmesh) return;
rcFree(pmesh->verts);
rcFree(pmesh->polys);
rcFree(pmesh->regs);
rcFree(pmesh->flags);
rcFree(pmesh->areas);
rcFree(pmesh);
}
rcPolyMeshDetail* rcAllocPolyMeshDetail()
{
rcPolyMeshDetail* dmesh = (rcPolyMeshDetail*)rcAlloc(sizeof(rcPolyMeshDetail), RC_ALLOC_PERM);
memset(dmesh, 0, sizeof(rcPolyMeshDetail));
return dmesh;
}
void rcFreePolyMeshDetail(rcPolyMeshDetail* dmesh)
{
if (!dmesh) return;
rcFree(dmesh->meshes);
rcFree(dmesh->verts);
rcFree(dmesh->tris);
rcFree(dmesh);
}
void rcCalcBounds(const float* verts, int nv, float* bmin, float* bmax)
{
// Calculate bounding box.
vcopy(bmin, verts);
vcopy(bmax, verts);
rcVcopy(bmin, verts);
rcVcopy(bmax, verts);
for (int i = 1; i < nv; ++i)
{
const float* v = &verts[i*3];
vmin(bmin, v);
vmax(bmax, v);
rcVmin(bmin, v);
rcVmax(bmax, v);
}
}
@@ -60,17 +203,25 @@ void rcCalcGridSize(const float* bmin, const float* bmax, float cs, int* w, int*
*h = (int)((bmax[2] - bmin[2])/cs+0.5f);
}
bool rcCreateHeightfield(rcHeightfield& hf, int width, int height,
/// @par
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocHeightfield, rcHeightfield
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);
hf.width = width;
hf.height = height;
hf.spans = new rcSpan*[hf.width*hf.height];
vcopy(hf.bmin, bmin);
vcopy(hf.bmax, bmax);
rcVcopy(hf.bmin, bmin);
rcVcopy(hf.bmax, bmax);
hf.cs = cs;
hf.ch = ch;
hf.spans = (rcSpan**)rcAlloc(sizeof(rcSpan*)*hf.width*hf.height, RC_ALLOC_PERM);
if (!hf.spans)
return false;
memset(hf.spans, 0, sizeof(rcSpan*)*hf.width*hf.height);
@@ -80,18 +231,29 @@ bool rcCreateHeightfield(rcHeightfield& hf, int width, int height,
static void calcTriNormal(const float* v0, const float* v1, const float* v2, float* norm)
{
float e0[3], e1[3];
vsub(e0, v1, v0);
vsub(e1, v2, v0);
vcross(norm, e0, e1);
vnormalize(norm);
rcVsub(e0, v1, v0);
rcVsub(e1, v2, v0);
rcVcross(norm, e0, e1);
rcVnormalize(norm);
}
void rcMarkWalkableTriangles(const float walkableSlopeAngle,
const float* verts, int nv,
/// @par
///
/// Only sets the aread 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,
const float* verts, int /*nv*/,
const int* tris, int nt,
unsigned char* flags)
unsigned char* areas)
{
const float walkableThr = cosf(walkableSlopeAngle/180.0f*(float)M_PI);
// TODO: VC complains about unref formal variable, figure out a way to handle this better.
// rcAssert(ctx);
const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI);
float norm[3];
@@ -101,12 +263,45 @@ void rcMarkWalkableTriangles(const float walkableSlopeAngle,
calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm);
// Check if the face is walkable.
if (norm[1] > walkableThr)
flags[i] |= RC_WALKABLE;
areas[i] = RC_WALKABLE_AREA;
}
}
static int getSpanCount(unsigned char flags, rcHeightfield& hf)
/// @par
///
/// Only sets the aread 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,
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);
const float walkableThr = cosf(walkableSlopeAngle/180.0f*RC_PI);
float norm[3];
for (int i = 0; i < nt; ++i)
{
const int* tri = &tris[i*3];
calcTriNormal(&verts[tri[0]*3], &verts[tri[1]*3], &verts[tri[2]*3], norm);
// Check if the face is walkable.
if (norm[1] <= walkableThr)
areas[i] = RC_NULL_AREA;
}
}
int rcGetHeightFieldSpanCount(rcContext* /*ctx*/, rcHeightfield& hf)
{
// TODO: VC complains about unref formal variable, figure out a way to handle this better.
// rcAssert(ctx);
const int w = hf.width;
const int h = hf.height;
int spanCount = 0;
@@ -116,7 +311,7 @@ static int getSpanCount(unsigned char flags, rcHeightfield& hf)
{
for (rcSpan* s = hf.spans[x + y*w]; s; s = s->next)
{
if (s->flags == flags)
if (s->area != RC_NULL_AREA)
spanCount++;
}
}
@@ -124,21 +319,25 @@ static int getSpanCount(unsigned char flags, rcHeightfield& hf)
return spanCount;
}
inline void setCon(rcCompactSpan& s, int dir, int i)
/// @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.
/// E.g: #rcBuildDistanceField and #rcBuildRegions
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig
bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb,
rcHeightfield& hf, rcCompactHeightfield& chf)
{
s.con &= ~(0xf << (dir*4));
s.con |= (i&0xf) << (dir*4);
}
bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb,
unsigned char flags, rcHeightfield& hf,
rcCompactHeightfield& chf)
{
rcTimeVal startTime = rcGetPerformanceTimer();
rcAssert(ctx);
ctx->startTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD);
const int w = hf.width;
const int h = hf.height;
const int spanCount = getSpanCount(flags, hf);
const int spanCount = rcGetHeightFieldSpanCount(ctx, hf);
// Fill in header.
chf.width = w;
@@ -147,27 +346,32 @@ bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb
chf.walkableHeight = walkableHeight;
chf.walkableClimb = walkableClimb;
chf.maxRegions = 0;
vcopy(chf.bmin, hf.bmin);
vcopy(chf.bmax, hf.bmax);
rcVcopy(chf.bmin, hf.bmin);
rcVcopy(chf.bmax, hf.bmax);
chf.bmax[1] += walkableHeight*hf.ch;
chf.cs = hf.cs;
chf.ch = hf.ch;
chf.cells = new rcCompactCell[w*h];
chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM);
if (!chf.cells)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h);
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h);
return false;
}
memset(chf.cells, 0, sizeof(rcCompactCell)*w*h);
chf.spans = new rcCompactSpan[spanCount];
chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM);
if (!chf.spans)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount);
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount);
return false;
}
memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount);
chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM);
if (!chf.areas)
{
ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount);
return false;
}
memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount);
const int MAX_HEIGHT = 0xffff;
@@ -185,12 +389,13 @@ bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb
c.count = 0;
while (s)
{
if (s->flags == flags)
if (s->area != RC_NULL_AREA)
{
const int bot = (int)s->smax;
const int top = s->next ? (int)s->next->smin : MAX_HEIGHT;
chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff);
chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff);
chf.areas[idx] = s->area;
idx++;
c.count++;
}
@@ -200,6 +405,8 @@ bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb
}
// Find neighbour connections.
const int MAX_LAYERS = RC_NOT_CONNECTED-1;
int tooHighNeighbour = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
@@ -208,14 +415,16 @@ bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
rcCompactSpan& s = chf.spans[i];
for (int dir = 0; dir < 4; ++dir)
{
setCon(s, dir, 0xf);
rcSetCon(s, dir, RC_NOT_CONNECTED);
const int nx = x + rcGetDirOffsetX(dir);
const int ny = y + rcGetDirOffsetY(dir);
// First check that the neighbour cell is in bounds.
if (nx < 0 || ny < 0 || nx >= w || ny >= h)
continue;
// Iterate over all neighbour spans and check if any of the is
// accessible from current cell.
const rcCompactCell& nc = chf.cells[nx+ny*w];
@@ -230,23 +439,34 @@ bool rcBuildCompactHeightfield(const int walkableHeight, const int walkableClimb
if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb)
{
// Mark direction as walkable.
setCon(s, dir, k - (int)nc.index);
const int idx = k - (int)nc.index;
if (idx < 0 || idx > MAX_LAYERS)
{
tooHighNeighbour = rcMax(tooHighNeighbour, idx);
continue;
}
rcSetCon(s, dir, idx);
break;
}
}
}
}
}
}
rcTimeVal endTime = rcGetPerformanceTimer();
if (rcGetBuildTimes())
rcGetBuildTimes()->buildCompact += rcGetDeltaTimeUsec(startTime, endTime);
if (tooHighNeighbour > MAX_LAYERS)
{
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;
}
/*
static int getHeightfieldMemoryUsage(const rcHeightfield& hf)
{
int size = 0;
@@ -270,3 +490,4 @@ static int getCompactHeightFieldMemoryusage(const rcCompactHeightfield& chf)
size += sizeof(rcCompactCell) * chf.width * chf.height;
return size;
}
*/

View File

@@ -0,0 +1,88 @@
//
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//
#include <stdlib.h>
#include <string.h>
#include "RecastAlloc.h"
static void *rcAllocDefault(int size, rcAllocHint)
{
return malloc(size);
}
static void rcFreeDefault(void *ptr)
{
free(ptr);
}
static rcAllocFunc* sRecastAllocFunc = rcAllocDefault;
static rcFreeFunc* sRecastFreeFunc = rcFreeDefault;
/// @see rcAlloc, rcFree
void rcAllocSetCustom(rcAllocFunc *allocFunc, rcFreeFunc *freeFunc)
{
sRecastAllocFunc = allocFunc ? allocFunc : rcAllocDefault;
sRecastFreeFunc = freeFunc ? freeFunc : rcFreeDefault;
}
/// @see rcAllocSetCustom
void* rcAlloc(int size, rcAllocHint hint)
{
return sRecastAllocFunc(size, hint);
}
/// @par
///
/// @warning This function leaves the value of @p ptr unchanged. So it still
/// points to the same (now invalid) location, and not to null.
///
/// @see rcAllocSetCustom
void rcFree(void* ptr)
{
if (ptr)
sRecastFreeFunc(ptr);
}
/// @class rcIntArray
///
/// While it is possible to pre-allocate a specific array size during
/// construction or by using the #resize method, certain methods will
/// automatically resize the array as needed.
///
/// @warning The array memory is not initialized to zero when the size is
/// manually set during construction or when using #resize.
/// @par
///
/// 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)
{
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;
}

View File

@@ -0,0 +1,524 @@
//
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//
#include <float.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "Recast.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"
/// @par
///
/// Basically, any spans that are closer to a boundary or obstruction than the specified radius
/// are marked as unwalkable.
///
/// This method is usually called immediately after the heightfield has been built.
///
/// @see rcCompactHeightfield, rcBuildCompactHeightfield, rcConfig::walkableRadius
bool rcErodeWalkableArea(rcContext* ctx, int radius, rcCompactHeightfield& chf)
{
rcAssert(ctx);
const int w = chf.width;
const int h = chf.height;
ctx->startTimer(RC_TIMER_ERODE_AREA);
unsigned char* dist = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP);
if (!dist)
{
ctx->log(RC_LOG_ERROR, "erodeWalkableArea: Out of memory 'dist' (%d).", chf.spanCount);
return false;
}
// Init distance.
memset(dist, 0xff, sizeof(unsigned char)*chf.spanCount);
// Mark boundary cells.
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++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 (chf.areas[i] == RC_NULL_AREA)
{
dist[i] = 0;
}
else
{
const rcCompactSpan& s = chf.spans[i];
int nc = 0;
for (int dir = 0; dir < 4; ++dir)
{
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
{
const int nx = x + rcGetDirOffsetX(dir);
const int ny = y + rcGetDirOffsetY(dir);
const int ni = (int)chf.cells[nx+ny*w].index + rcGetCon(s, dir);
if (chf.areas[ni] != RC_NULL_AREA)
{
nc++;
}
}
}
// At least one missing neighbour.
if (nc != 4)
dist[i] = 0;
}
}
}
}
unsigned char nd;
// Pass 1
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++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 (rcGetCon(s, 0) != RC_NOT_CONNECTED)
{
// (-1,0)
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);
const rcCompactSpan& as = chf.spans[ai];
nd = (unsigned char)rcMin((int)dist[ai]+2, 255);
if (nd < dist[i])
dist[i] = nd;
// (-1,-1)
if (rcGetCon(as, 3) != RC_NOT_CONNECTED)
{
const int aax = ax + rcGetDirOffsetX(3);
const int aay = ay + rcGetDirOffsetY(3);
const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 3);
nd = (unsigned char)rcMin((int)dist[aai]+3, 255);
if (nd < dist[i])
dist[i] = nd;
}
}
if (rcGetCon(s, 3) != RC_NOT_CONNECTED)
{
// (0,-1)
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);
const rcCompactSpan& as = chf.spans[ai];
nd = (unsigned char)rcMin((int)dist[ai]+2, 255);
if (nd < dist[i])
dist[i] = nd;
// (1,-1)
if (rcGetCon(as, 2) != RC_NOT_CONNECTED)
{
const int aax = ax + rcGetDirOffsetX(2);
const int aay = ay + rcGetDirOffsetY(2);
const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 2);
nd = (unsigned char)rcMin((int)dist[aai]+3, 255);
if (nd < dist[i])
dist[i] = nd;
}
}
}
}
}
// Pass 2
for (int y = h-1; y >= 0; --y)
{
for (int x = w-1; x >= 0; --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 (rcGetCon(s, 2) != RC_NOT_CONNECTED)
{
// (1,0)
const int ax = x + rcGetDirOffsetX(2);
const int ay = y + rcGetDirOffsetY(2);
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 2);
const rcCompactSpan& as = chf.spans[ai];
nd = (unsigned char)rcMin((int)dist[ai]+2, 255);
if (nd < dist[i])
dist[i] = nd;
// (1,1)
if (rcGetCon(as, 1) != RC_NOT_CONNECTED)
{
const int aax = ax + rcGetDirOffsetX(1);
const int aay = ay + rcGetDirOffsetY(1);
const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 1);
nd = (unsigned char)rcMin((int)dist[aai]+3, 255);
if (nd < dist[i])
dist[i] = nd;
}
}
if (rcGetCon(s, 1) != RC_NOT_CONNECTED)
{
// (0,1)
const int ax = x + rcGetDirOffsetX(1);
const int ay = y + rcGetDirOffsetY(1);
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 1);
const rcCompactSpan& as = chf.spans[ai];
nd = (unsigned char)rcMin((int)dist[ai]+2, 255);
if (nd < dist[i])
dist[i] = nd;
// (-1,1)
if (rcGetCon(as, 0) != RC_NOT_CONNECTED)
{
const int aax = ax + rcGetDirOffsetX(0);
const int aay = ay + rcGetDirOffsetY(0);
const int aai = (int)chf.cells[aax+aay*w].index + rcGetCon(as, 0);
nd = (unsigned char)rcMin((int)dist[aai]+3, 255);
if (nd < dist[i])
dist[i] = nd;
}
}
}
}
}
const unsigned char thr = (unsigned char)(radius*2);
for (int i = 0; i < chf.spanCount; ++i)
if (dist[i] < thr)
chf.areas[i] = RC_NULL_AREA;
rcFree(dist);
ctx->stopTimer(RC_TIMER_ERODE_AREA);
return true;
}
static void insertSort(unsigned char* a, const int n)
{
int i, j;
for (i = 1; i < n; i++)
{
const unsigned char value = a[i];
for (j = i - 1; j >= 0 && a[j] > value; j--)
a[j+1] = a[j];
a[j+1] = value;
}
}
/// @par
///
/// This filter is usually applied after applying area id's using functions
/// such as #rcMarkBoxArea, #rcMarkConvexPolyArea, and #rcMarkCylinderArea.
///
/// @see rcCompactHeightfield
bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf)
{
rcAssert(ctx);
const int w = chf.width;
const int h = chf.height;
ctx->startTimer(RC_TIMER_MEDIAN_AREA);
unsigned char* areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP);
if (!areas)
{
ctx->log(RC_LOG_ERROR, "medianFilterWalkableArea: Out of memory 'areas' (%d).", chf.spanCount);
return false;
}
// Init distance.
memset(areas, 0xff, sizeof(unsigned char)*chf.spanCount);
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++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)
{
areas[i] = chf.areas[i];
continue;
}
unsigned char nei[9];
for (int j = 0; j < 9; ++j)
nei[j] = chf.areas[i];
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);
if (chf.areas[ai] != RC_NULL_AREA)
nei[dir*2+0] = chf.areas[ai];
const rcCompactSpan& as = chf.spans[ai];
const int dir2 = (dir+1) & 0x3;
if (rcGetCon(as, dir2) != RC_NOT_CONNECTED)
{
const int ax2 = ax + rcGetDirOffsetX(dir2);
const int ay2 = ay + rcGetDirOffsetY(dir2);
const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);
if (chf.areas[ai2] != RC_NULL_AREA)
nei[dir*2+1] = chf.areas[ai2];
}
}
}
insertSort(nei, 9);
areas[i] = nei[4];
}
}
}
memcpy(chf.areas, areas, sizeof(unsigned char)*chf.spanCount);
rcFree(areas);
ctx->stopTimer(RC_TIMER_MEDIAN_AREA);
return true;
}
/// @par
///
/// The value of spacial parameters are in world units.
///
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
void rcMarkBoxArea(rcContext* ctx, const float* bmin, const float* bmax, unsigned char areaId,
rcCompactHeightfield& chf)
{
rcAssert(ctx);
ctx->startTimer(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);
int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs);
int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs);
int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch);
int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs);
if (maxx < 0) return;
if (minx >= chf.width) return;
if (maxz < 0) return;
if (minz >= chf.height) return;
if (minx < 0) minx = 0;
if (maxx >= chf.width) maxx = chf.width-1;
if (minz < 0) minz = 0;
if (maxz >= chf.height) maxz = chf.height-1;
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
const rcCompactCell& c = chf.cells[x+z*chf.width];
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
rcCompactSpan& s = chf.spans[i];
if ((int)s.y >= miny && (int)s.y <= maxy)
{
if (chf.areas[i] != RC_NULL_AREA)
chf.areas[i] = areaId;
}
}
}
}
ctx->stopTimer(RC_TIMER_MARK_BOX_AREA);
}
static int pointInPoly(int nvert, const float* verts, const float* p)
{
int i, j, c = 0;
for (i = 0, j = nvert-1; i < nvert; j = i++)
{
const float* vi = &verts[i*3];
const float* vj = &verts[j*3];
if (((vi[2] > p[2]) != (vj[2] > p[2])) &&
(p[0] < (vj[0]-vi[0]) * (p[2]-vi[2]) / (vj[2]-vi[2]) + vi[0]) )
c = !c;
}
return c;
}
/// @par
///
/// The value of spacial parameters are in world units.
///
/// The y-values of the polygon vertices are ignored. So the polygon is effectively
/// projected onto the xz-plane at @p hmin, then extruded to @p hmax.
///
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts,
const float hmin, const float hmax, unsigned char areaId,
rcCompactHeightfield& chf)
{
rcAssert(ctx);
ctx->startTimer(RC_TIMER_MARK_CONVEXPOLY_AREA);
float bmin[3], bmax[3];
rcVcopy(bmin, verts);
rcVcopy(bmax, verts);
for (int i = 1; i < nverts; ++i)
{
rcVmin(bmin, &verts[i*3]);
rcVmax(bmax, &verts[i*3]);
}
bmin[1] = hmin;
bmax[1] = hmax;
int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs);
int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch);
int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs);
int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs);
int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch);
int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs);
if (maxx < 0) return;
if (minx >= chf.width) return;
if (maxz < 0) return;
if (minz >= chf.height) return;
if (minx < 0) minx = 0;
if (maxx >= chf.width) maxx = chf.width-1;
if (minz < 0) minz = 0;
if (maxz >= chf.height) maxz = chf.height-1;
// TODO: Optimize.
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
const rcCompactCell& c = chf.cells[x+z*chf.width];
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
rcCompactSpan& s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
continue;
if ((int)s.y >= miny && (int)s.y <= maxy)
{
float p[3];
p[0] = chf.bmin[0] + (x+0.5f)*chf.cs;
p[1] = 0;
p[2] = chf.bmin[2] + (z+0.5f)*chf.cs;
if (pointInPoly(nverts, verts, p))
{
chf.areas[i] = areaId;
}
}
}
}
}
ctx->stopTimer(RC_TIMER_MARK_CONVEXPOLY_AREA);
}
/// @par
///
/// The value of spacial parameters are in world units.
///
/// @see rcCompactHeightfield, rcMedianFilterWalkableArea
void rcMarkCylinderArea(rcContext* ctx, const float* pos,
const float r, const float h, unsigned char areaId,
rcCompactHeightfield& chf)
{
rcAssert(ctx);
ctx->startTimer(RC_TIMER_MARK_CYLINDER_AREA);
float bmin[3], bmax[3];
bmin[0] = pos[0] - r;
bmin[1] = pos[1];
bmin[2] = pos[2] - r;
bmax[0] = pos[0] + r;
bmax[1] = pos[1] + h;
bmax[2] = pos[2] + r;
const float r2 = r*r;
int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs);
int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch);
int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs);
int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs);
int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch);
int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs);
if (maxx < 0) return;
if (minx >= chf.width) return;
if (maxz < 0) return;
if (minz >= chf.height) return;
if (minx < 0) minx = 0;
if (maxx >= chf.width) maxx = chf.width-1;
if (minz < 0) minz = 0;
if (maxz >= chf.height) maxz = chf.height-1;
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
const rcCompactCell& c = chf.cells[x+z*chf.width];
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
rcCompactSpan& s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
continue;
if ((int)s.y >= miny && (int)s.y <= maxy)
{
const float sx = chf.bmin[0] + (x+0.5f)*chf.cs;
const float sz = chf.bmin[2] + (z+0.5f)*chf.cs;
const float dx = sx - pos[0];
const float dz = sz - pos[2];
if (dx*dx + dz*dz < r2)
{
chf.areas[i] = areaId;
}
}
}
}
}
ctx->stopTimer(RC_TIMER_MARK_CYLINDER_AREA);
}

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2009 Mikko Mononen memon@inside.org
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
@@ -21,8 +21,8 @@
#include <string.h>
#include <stdio.h>
#include "Recast.h"
#include "RecastLog.h"
#include "RecastTimer.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"
static int getCornerHeight(int x, int y, int i, int dir,
@@ -33,44 +33,46 @@ static int getCornerHeight(int x, int y, int i, int dir,
int ch = (int)s.y;
int dirp = (dir+1) & 0x3;
unsigned short regs[4] = {0,0,0,0};
unsigned int regs[4] = {0,0,0,0};
regs[0] = s.reg;
// Combine region and area codes in order to prevent
// 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) != 0xf)
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];
ch = rcMax(ch, (int)as.y);
regs[1] = as.reg;
if (rcGetCon(as, dirp) != 0xf)
regs[1] = chf.spans[ai].reg | (chf.areas[ai] << 16);
if (rcGetCon(as, dirp) != RC_NOT_CONNECTED)
{
const int ax2 = ax + rcGetDirOffsetX(dirp);
const int ay2 = ay + rcGetDirOffsetY(dirp);
const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dirp);
const rcCompactSpan& as2 = chf.spans[ai2];
ch = rcMax(ch, (int)as2.y);
regs[2] = as2.reg;
regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16);
}
}
if (rcGetCon(s, dirp) != 0xf)
if (rcGetCon(s, dirp) != RC_NOT_CONNECTED)
{
const int ax = x + rcGetDirOffsetX(dirp);
const int ay = y + rcGetDirOffsetY(dirp);
const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dirp);
const rcCompactSpan& as = chf.spans[ai];
ch = rcMax(ch, (int)as.y);
regs[3] = as.reg;
if (rcGetCon(as, dir) != 0xf)
regs[3] = chf.spans[ai].reg | (chf.areas[ai] << 16);
if (rcGetCon(as, dir) != RC_NOT_CONNECTED)
{
const int ax2 = ax + rcGetDirOffsetX(dir);
const int ay2 = ay + rcGetDirOffsetY(dir);
const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dir);
const rcCompactSpan& as2 = chf.spans[ai2];
ch = rcMax(ch, (int)as2.y);
regs[2] = as2.reg;
regs[2] = chf.spans[ai2].reg | (chf.areas[ai2] << 16);
}
}
@@ -86,8 +88,9 @@ static int getCornerHeight(int x, int y, int i, int dir,
// followed by two interior cells and none of the regions are out of bounds.
const bool twoSameExts = (regs[a] & regs[b] & RC_BORDER_REG) != 0 && regs[a] == regs[b];
const bool twoInts = ((regs[c] | regs[d]) & RC_BORDER_REG) == 0;
const bool intsSameArea = (regs[c]>>16) == (regs[d]>>16);
const bool noZeros = regs[a] != 0 && regs[b] != 0 && regs[c] != 0 && regs[d] != 0;
if (twoSameExts && twoInts && noZeros)
if (twoSameExts && twoInts && intsSameArea && noZeros)
{
isBorderVertex = true;
break;
@@ -109,6 +112,8 @@ static void walkContour(int x, int y, int i,
unsigned char startDir = dir;
int starti = i;
const unsigned char area = chf.areas[i];
int iter = 0;
while (++iter < 40000)
{
@@ -116,6 +121,7 @@ static void walkContour(int x, int y, int i,
{
// Choose the edge corner
bool isBorderVertex = false;
bool isAreaBorder = false;
int px = x;
int py = getCornerHeight(x, y, i, dir, chf, isBorderVertex);
int pz = y;
@@ -127,16 +133,19 @@ static void walkContour(int x, int y, int i,
}
int r = 0;
const rcCompactSpan& s = chf.spans[i];
if (rcGetCon(s, dir) != 0xf)
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];
r = (int)as.reg;
r = (int)chf.spans[ai].reg;
if (area != chf.areas[ai])
isAreaBorder = true;
}
if (isBorderVertex)
r |= RC_BORDER_VERTEX;
if (isAreaBorder)
r |= RC_AREA_BORDER;
points.push(px);
points.push(py);
points.push(pz);
@@ -151,7 +160,7 @@ static void walkContour(int x, int y, int i,
const int nx = x + rcGetDirOffsetX(dir);
const int ny = y + rcGetDirOffsetY(dir);
const rcCompactSpan& s = chf.spans[i];
if (rcGetCon(s, dir) != 0xf)
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
{
const rcCompactCell& nc = chf.cells[nx+ny*chf.width];
ni = (int)nc.index + rcGetCon(s, dir);
@@ -174,9 +183,9 @@ static void walkContour(int x, int y, int i,
}
}
static float distancePtSeg(int x, int y, int z,
int px, int py, int pz,
int qx, int qy, int qz)
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);
@@ -218,20 +227,40 @@ static float distancePtSeg(int x, int y, int z,
return dx*dx + dz*dz;
}
static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float maxError, int maxEdgeLen)
static void simplifyContour(rcIntArray& points, rcIntArray& simplified,
const float maxError, const int maxEdgeLen, const int buildFlags)
{
// Add initial points.
bool noConnections = true;
bool hasConnections = false;
for (int i = 0; i < points.size(); i += 4)
{
if ((points[i+3] & 0xffff) != 0)
if ((points[i+3] & RC_CONTOUR_REG_MASK) != 0)
{
noConnections = false;
hasConnections = true;
break;
}
}
if (noConnections)
if (hasConnections)
{
// The contour has some portals to other regions.
// Add a new point to every location where the region changes.
for (int i = 0, ni = points.size()/4; i < ni; ++i)
{
int ii = (i+1) % ni;
const bool differentRegs = (points[i*4+3] & RC_CONTOUR_REG_MASK) != (points[ii*4+3] & RC_CONTOUR_REG_MASK);
const bool areaBorders = (points[i*4+3] & RC_AREA_BORDER) != (points[ii*4+3] & RC_AREA_BORDER);
if (differentRegs || areaBorders)
{
simplified.push(points[i*4+0]);
simplified.push(points[i*4+1]);
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.
@@ -256,7 +285,7 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma
llz = z;
lli = i/4;
}
if (x >= urx || (x == urx && z > urz))
if (x > urx || (x == urx && z > urz))
{
urx = x;
ury = y;
@@ -274,22 +303,6 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma
simplified.push(urz);
simplified.push(uri);
}
else
{
// The contour has some portals to other regions.
// Add a new point to every location where the region changes.
for (int i = 0, ni = points.size()/4; i < ni; ++i)
{
int ii = (i+1) % ni;
if ((points[i*4+3] & 0xffff) != (points[ii*4+3] & 0xffff))
{
simplified.push(points[i*4+0]);
simplified.push(points[i*4+1]);
simplified.push(points[i*4+2]);
simplified.push(i);
}
}
}
// Add points until all raw points are within
// error tolerance to the simplified shape.
@@ -298,34 +311,48 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma
{
int ii = (i+1) % (simplified.size()/4);
int ax = simplified[i*4+0];
int ay = simplified[i*4+1];
int az = simplified[i*4+2];
int ai = simplified[i*4+3];
int bx = simplified[ii*4+0];
int by = simplified[ii*4+1];
int bz = simplified[ii*4+2];
int bi = simplified[ii*4+3];
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];
// Find maximum deviation from the segment.
float maxd = 0;
int maxi = -1;
int ci = (ai+1) % pn;
int ci, cinc, endi;
// Tesselate only outer edges.
if ((points[ci*4+3] & 0xffff) == 0)
// Traverse the segment in lexilogical order so that the
// max deviation is calculated similarly when traversing
// opposite segments.
if (bx > ax || (bx == ax && bz > az))
{
while (ci != bi)
cinc = 1;
ci = (ai+cinc) % pn;
endi = bi;
}
else
{
cinc = pn-1;
ci = (bi+cinc) % pn;
endi = ai;
}
// Tessellate only outer edges or edges between areas.
if ((points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0 ||
(points[ci*4+3] & RC_AREA_BORDER))
{
while (ci != endi)
{
float d = distancePtSeg(points[ci*4+0], points[ci*4+1]/4, points[ci*4+2],
ax, ay/4, az, bx, by/4, bz);
float d = distancePtSeg(points[ci*4+0], points[ci*4+2], ax, az, bx, bz);
if (d > maxd)
{
maxd = d;
maxi = ci;
}
ci = (ci+1) % pn;
ci = (ci+cinc) % pn;
}
}
@@ -336,7 +363,7 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma
{
// Add space for the new point.
simplified.resize(simplified.size()+4);
int n = simplified.size()/4;
const int n = simplified.size()/4;
for (int j = n-1; j > i; --j)
{
simplified[j*4+0] = simplified[(j-1)*4+0];
@@ -357,33 +384,52 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma
}
// Split too long edges.
if (maxEdgeLen > 0)
if (maxEdgeLen > 0 && (buildFlags & (RC_CONTOUR_TESS_WALL_EDGES|RC_CONTOUR_TESS_AREA_EDGES)) != 0)
{
for (int i = 0; i < simplified.size()/4; )
{
int ii = (i+1) % (simplified.size()/4);
const int ii = (i+1) % (simplified.size()/4);
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];
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];
// 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.
if ((buildFlags & RC_CONTOUR_TESS_WALL_EDGES) && (points[ci*4+3] & RC_CONTOUR_REG_MASK) == 0)
tess = true;
// Edges between areas.
if ((buildFlags & RC_CONTOUR_TESS_AREA_EDGES) && (points[ci*4+3] & RC_AREA_BORDER))
tess = true;
// Tesselate only outer edges.
if ((points[ci*4+3] & 0xffff) == 0)
if (tess)
{
int dx = bx - ax;
int dz = bz - az;
if (dx*dx + dz*dz > maxEdgeLen*maxEdgeLen)
{
int n = bi < ai ? (bi+pn - ai) : (bi - ai);
maxi = (ai + n/2) % pn;
// Round based on the segments in lexilogical order so that the
// max tesselation is consistent regardles in which direction
// segments are traversed.
if (bx > ax || (bx == ax && bz > az))
{
const int n = bi < ai ? (bi+pn - ai) : (bi - ai);
maxi = (ai + n/2) % pn;
}
else
{
const int n = bi < ai ? (bi+pn - ai) : (bi - ai);
maxi = (ai + (n+1)/2) % pn;
}
}
}
@@ -393,7 +439,7 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma
{
// Add space for the new point.
simplified.resize(simplified.size()+4);
int n = simplified.size()/4;
const int n = simplified.size()/4;
for (int j = n-1; j > i; --j)
{
simplified[j*4+0] = simplified[(j-1)*4+0];
@@ -420,7 +466,7 @@ static void simplifyContour(rcIntArray& points, rcIntArray& simplified, float ma
// and the neighbour region is take from the next raw point.
const int ai = (simplified[i*4+3]+1) % pn;
const int bi = simplified[i*4+3];
simplified[i*4+3] = (points[ai*4+3] & 0xffff) | (points[bi*4+3] & RC_BORDER_VERTEX);
simplified[i*4+3] = (points[ai*4+3] & RC_CONTOUR_REG_MASK) | (points[bi*4+3] & RC_BORDER_VERTEX);
}
}
@@ -446,7 +492,7 @@ static void removeDegenerateSegments(rcIntArray& simplified)
simplified[j*4+2] = simplified[(j+1)*4+2];
simplified[j*4+3] = simplified[(j+1)*4+3];
}
simplified.pop();
simplified.resize(simplified.size()-4);
}
}
}
@@ -463,25 +509,40 @@ 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)
{
return (b[0] - a[0]) * (c[2] - a[2]) - (c[0] - a[0]) * (b[2] - a[2]) <= 0;
}
static void getClosestIndices(const int* vertsa, const int nvertsa,
const int* vertsb, const int nvertsb,
int& ia, int& ib)
{
int closestDist = 0xfffffff;
ia = -1, ib = -1;
for (int i = 0; i < nvertsa; ++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];
for (int j = 0; j < nvertsb; ++j)
{
const int* vb = &vertsb[j*4];
const int dx = vb[0] - va[0];
const int dz = vb[2] - va[2];
const int d = dx*dx + dz*dz;
if (d < closestDist)
// vb must be "infront" of va.
if (ileft(vap,va,vb) && ileft(va,van,vb))
{
ia = i;
ib = j;
closestDist = d;
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;
}
}
}
}
@@ -490,7 +551,7 @@ static void getClosestIndices(const int* vertsa, const int nvertsa,
static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib)
{
const int maxVerts = ca.nverts + cb.nverts + 2;
int* verts = new int[maxVerts*4];
int* verts = (int*)rcAlloc(sizeof(int)*maxVerts*4, RC_ALLOC_PERM);
if (!verts)
return false;
@@ -520,47 +581,73 @@ static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib)
nv++;
}
delete [] ca.verts;
rcFree(ca.verts);
ca.verts = verts;
ca.nverts = nv;
delete [] cb.verts;
rcFree(cb.verts);
cb.verts = 0;
cb.nverts = 0;
return true;
}
bool rcBuildContours(rcCompactHeightfield& chf,
/// @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.
/// (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,
rcContourSet& cset)
rcContourSet& cset, const int buildFlags)
{
rcAssert(ctx);
const int w = chf.width;
const int h = chf.height;
const int borderSize = chf.borderSize;
rcTimeVal startTime = rcGetPerformanceTimer();
ctx->startTimer(RC_TIMER_BUILD_CONTOURS);
vcopy(cset.bmin, chf.bmin);
vcopy(cset.bmax, chf.bmax);
rcVcopy(cset.bmin, chf.bmin);
rcVcopy(cset.bmax, chf.bmax);
if (borderSize > 0)
{
// If the heightfield was build with bordersize, remove the offset.
const float pad = borderSize*chf.cs;
cset.bmin[0] += pad;
cset.bmin[2] += pad;
cset.bmax[0] -= pad;
cset.bmax[2] -= pad;
}
cset.cs = chf.cs;
cset.ch = chf.ch;
cset.width = chf.width - chf.borderSize*2;
cset.height = chf.height - chf.borderSize*2;
cset.borderSize = chf.borderSize;
const int maxContours = chf.maxRegions*2;
cset.conts = new rcContour[maxContours];
int maxContours = rcMax((int)chf.maxRegions, 8);
cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM);
if (!cset.conts)
return false;
cset.nconts = 0;
unsigned char* flags = new unsigned char[chf.spanCount];
rcScopedDelete<unsigned char> flags = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP);
if (!flags)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags'.");
ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' (%d).", chf.spanCount);
return false;
}
rcTimeVal traceStartTime = rcGetPerformanceTimer();
ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE);
// Mark boundaries.
for (int y = 0; y < h; ++y)
@@ -572,7 +659,7 @@ bool rcBuildContours(rcCompactHeightfield& chf,
{
unsigned char res = 0;
const rcCompactSpan& s = chf.spans[i];
if (!s.reg || (s.reg & RC_BORDER_REG))
if (!chf.spans[i].reg || (chf.spans[i].reg & RC_BORDER_REG))
{
flags[i] = 0;
continue;
@@ -580,15 +667,14 @@ bool rcBuildContours(rcCompactHeightfield& chf,
for (int dir = 0; dir < 4; ++dir)
{
unsigned short r = 0;
if (rcGetCon(s, dir) != 0xf)
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 rcCompactSpan& as = chf.spans[ai];
r = as.reg;
r = chf.spans[ai].reg;
}
if (r == s.reg)
if (r == chf.spans[i].reg)
res |= (1 << dir);
}
flags[i] = res ^ 0xf; // Inverse, mark non connected edges.
@@ -596,9 +682,7 @@ bool rcBuildContours(rcCompactHeightfield& chf,
}
}
rcTimeVal traceEndTime = rcGetPerformanceTimer();
rcTimeVal simplifyStartTime = rcGetPerformanceTimer();
ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE);
rcIntArray verts(256);
rcIntArray simplified(64);
@@ -615,36 +699,87 @@ bool rcBuildContours(rcCompactHeightfield& chf,
flags[i] = 0;
continue;
}
unsigned short reg = chf.spans[i].reg;
const unsigned short reg = chf.spans[i].reg;
if (!reg || (reg & RC_BORDER_REG))
continue;
const unsigned char area = chf.areas[i];
verts.resize(0);
simplified.resize(0);
ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE);
walkContour(x, y, i, chf, flags, verts);
simplifyContour(verts, simplified, maxError, maxEdgeLen);
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)
{
if (cset.nconts >= maxContours)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildContours: Too many contours %d, max %d.", cset.nconts, maxContours);
return false;
// Allocate more contours.
// This can happen when there are tiny holes in the heightfield.
const int oldMax = maxContours;
maxContours *= 2;
rcContour* newConts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM);
for (int j = 0; j < cset.nconts; ++j)
{
newConts[j] = cset.conts[j];
// Reset source pointers to prevent data deletion.
cset.conts[j].verts = 0;
cset.conts[j].rverts = 0;
}
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;
cont->verts = new int[cont->nverts*4];
cont->verts = (int*)rcAlloc(sizeof(int)*cont->nverts*4, RC_ALLOC_PERM);
if (!cont->verts)
{
ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' (%d).", cont->nverts);
return false;
}
memcpy(cont->verts, &simplified[0], sizeof(int)*cont->nverts*4);
if (borderSize > 0)
{
// If the heightfield was build with bordersize, remove the offset.
for (int i = 0; i < cont->nverts; ++i)
{
int* v = &cont->verts[i*4];
v[0] -= borderSize;
v[2] -= borderSize;
}
}
cont->nrverts = verts.size()/4;
cont->rverts = new int[cont->nrverts*4];
cont->rverts = (int*)rcAlloc(sizeof(int)*cont->nrverts*4, RC_ALLOC_PERM);
if (!cont->rverts)
{
ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' (%d).", cont->nrverts);
return false;
}
memcpy(cont->rverts, &verts[0], sizeof(int)*cont->nrverts*4);
if (borderSize > 0)
{
// If the heightfield was build with bordersize, remove the offset.
for (int i = 0; i < cont->nrverts; ++i)
{
int* v = &cont->rverts[i*4];
v[0] -= borderSize;
v[2] -= borderSize;
}
}
/* cont->cx = cont->cy = cont->cz = 0;
for (int i = 0; i < cont->nverts; ++i)
@@ -658,13 +793,14 @@ bool rcBuildContours(rcCompactHeightfield& chf,
cont->cz /= cont->nverts;*/
cont->reg = reg;
cont->area = area;
}
}
}
}
// Check and merge droppings.
// Sometimes the previous algorithms can fail and create several countours
// 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)
{
@@ -689,44 +825,29 @@ bool rcBuildContours(rcCompactHeightfield& chf,
}
if (mergeIdx == -1)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "rcBuildContours: Could not find merge target for bad contour %d.", i);
ctx->log(RC_LOG_WARNING, "rcBuildContours: Could not find merge target for bad contour %d.", i);
}
else
{
rcContour& mcont = cset.conts[mergeIdx];
// Merge by closest points.
int ia, ib;
int ia = 0, ib = 0;
getClosestIndices(mcont.verts, mcont.nverts, cont.verts, cont.nverts, ia, ib);
if (ia == -1 || ib == -1)
{
ctx->log(RC_LOG_WARNING, "rcBuildContours: Failed to find merge points for %d and %d.", i, mergeIdx);
continue;
}
if (!mergeContours(mcont, cont, ia, ib))
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "rcBuildContours: Failed to merge contours %d and %d.", i, mergeIdx);
ctx->log(RC_LOG_WARNING, "rcBuildContours: Failed to merge contours %d and %d.", i, mergeIdx);
continue;
}
}
}
}
delete [] flags;
rcTimeVal simplifyEndTime = rcGetPerformanceTimer();
rcTimeVal endTime = rcGetPerformanceTimer();
// if (rcGetLog())
// {
// rcGetLog()->log(RC_LOG_PROGRESS, "Create contours: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
// rcGetLog()->log(RC_LOG_PROGRESS, " - boundary: %.3f ms", rcGetDeltaTimeUsec(boundaryStartTime, boundaryEndTime)/1000.0f);
// rcGetLog()->log(RC_LOG_PROGRESS, " - contour: %.3f ms", rcGetDeltaTimeUsec(contourStartTime, contourEndTime)/1000.0f);
// }
if (rcGetBuildTimes())
{
rcGetBuildTimes()->buildContours += rcGetDeltaTimeUsec(startTime, endTime);
rcGetBuildTimes()->buildContoursTrace += rcGetDeltaTimeUsec(traceStartTime, traceEndTime);
rcGetBuildTimes()->buildContoursSimplify += rcGetDeltaTimeUsec(simplifyStartTime, simplifyEndTime);
}
ctx->stopTimer(RC_TIMER_BUILD_CONTOURS);
return true;
}

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2009 Mikko Mononen memon@inside.org
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
@@ -20,15 +20,73 @@
#include <math.h>
#include <stdio.h>
#include "Recast.h"
#include "RecastLog.h"
#include "RecastTimer.h"
#include "RecastAssert.h"
/// @par
///
/// Allows the formation of walkable regions that will flow over low lying
/// objects such as curbs, and up structures such as stairways.
///
/// Two neighboring spans are walkable if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb</tt>
///
/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call
/// #rcFilterLedgeSpans after calling this filter.
///
/// @see rcHeightfield, rcConfig
void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid)
{
rcAssert(ctx);
void rcFilterLedgeSpans(const int walkableHeight,
const int walkableClimb,
ctx->startTimer(RC_TIMER_FILTER_LOW_OBSTACLES);
const int w = solid.width;
const int h = solid.height;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
rcSpan* ps = 0;
bool previousWalkable = false;
unsigned char previousArea = RC_NULL_AREA;
for (rcSpan* s = solid.spans[x + y*w]; s; ps = s, s = s->next)
{
const bool walkable = s->area != RC_NULL_AREA;
// If current span is not walkable, but there is walkable
// span just below it, mark the span above it walkable too.
if (!walkable && previousWalkable)
{
if (rcAbs((int)s->smax - (int)ps->smax) <= walkableClimb)
s->area = previousArea;
}
// Copy walkable flag so that it cannot propagate
// past multiple non-walkable objects.
previousWalkable = walkable;
previousArea = s->area;
}
}
}
ctx->stopTimer(RC_TIMER_FILTER_LOW_OBSTACLES);
}
/// @par
///
/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb
/// from the current span's maximum.
/// This method removes the impact of the overestimation of conservative voxelization
/// so the resulting mesh will not have regions hanging in the air over ledges.
///
/// A span is a ledge if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb</tt>
///
/// @see rcHeightfield, rcConfig
void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walkableClimb,
rcHeightfield& solid)
{
rcTimeVal startTime = rcGetPerformanceTimer();
rcAssert(ctx);
ctx->startTimer(RC_TIMER_FILTER_BORDER);
const int w = solid.width;
const int h = solid.height;
@@ -42,15 +100,19 @@ void rcFilterLedgeSpans(const int walkableHeight,
for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next)
{
// Skip non walkable spans.
if ((s->flags & RC_WALKABLE) == 0)
if (s->area == RC_NULL_AREA)
continue;
const int bot = (int)s->smax;
const int top = s->next ? (int)s->next->smin : MAX_HEIGHT;
const int bot = (int)(s->smax);
const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT;
// Find neighbours minimum height.
int minh = MAX_HEIGHT;
// Min and max height of accessible neighbours.
int asmin = s->smax;
int asmax = s->smax;
for (int dir = 0; dir < 4; ++dir)
{
int dx = x + rcGetDirOffsetX(dir);
@@ -77,30 +139,49 @@ void rcFilterLedgeSpans(const int walkableHeight,
ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT;
// Skip neightbour if the gap between the spans is too small.
if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight)
{
minh = rcMin(minh, nbot - bot);
// Find min/max accessible neighbour height.
if (rcAbs(nbot - bot) <= walkableClimb)
{
if (nbot < asmin) asmin = nbot;
if (nbot > asmax) asmax = nbot;
}
}
}
}
// The current span is close to a ledge if the drop to any
// neighbour span is less than the walkableClimb.
if (minh < -walkableClimb)
s->flags &= ~RC_WALKABLE;
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)
{
s->area = RC_NULL_AREA;
}
}
}
}
rcTimeVal endTime = rcGetPerformanceTimer();
// if (rcGetLog())
// rcGetLog()->log(RC_LOG_PROGRESS, "Filter border: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
if (rcGetBuildTimes())
rcGetBuildTimes()->filterBorder += rcGetDeltaTimeUsec(startTime, endTime);
ctx->stopTimer(RC_TIMER_FILTER_BORDER);
}
void rcFilterWalkableLowHeightSpans(int walkableHeight,
rcHeightfield& solid)
/// @par
///
/// For this filter, the clearance above the span is the distance from the span's
/// maximum to the next higher span's minimum. (Same grid column.)
///
/// @see rcHeightfield, rcConfig
void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid)
{
rcTimeVal startTime = rcGetPerformanceTimer();
rcAssert(ctx);
ctx->startTimer(RC_TIMER_FILTER_WALKABLE);
const int w = solid.width;
const int h = solid.height;
@@ -114,136 +195,13 @@ void rcFilterWalkableLowHeightSpans(int walkableHeight,
{
for (rcSpan* s = solid.spans[x + y*w]; s; s = s->next)
{
const int bot = (int)s->smax;
const int top = s->next ? (int)s->next->smin : MAX_HEIGHT;
const int bot = (int)(s->smax);
const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT;
if ((top - bot) <= walkableHeight)
s->flags &= ~RC_WALKABLE;
s->area = RC_NULL_AREA;
}
}
}
rcTimeVal endTime = rcGetPerformanceTimer();
// if (rcGetLog())
// rcGetLog()->log(RC_LOG_PROGRESS, "Filter walkable: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
if (rcGetBuildTimes())
rcGetBuildTimes()->filterWalkable += rcGetDeltaTimeUsec(startTime, endTime);
}
struct rcReachableSeed
{
inline void set(int ix, int iy, rcSpan* is)
{
x = (unsigned short)ix;
y = (unsigned short)iy;
s = is;
}
unsigned short x, y;
rcSpan* s;
};
bool rcMarkReachableSpans(const int walkableHeight,
const int walkableClimb,
rcHeightfield& solid)
{
const int w = solid.width;
const int h = solid.height;
const int MAX_HEIGHT = 0xffff;
rcTimeVal startTime = rcGetPerformanceTimer();
// Build navigable space.
const int MAX_SEEDS = w*h;
rcReachableSeed* stack = new rcReachableSeed[MAX_SEEDS];
if (!stack)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcMarkReachableSpans: Out of memory 'stack' (%d).", MAX_SEEDS);
return false;
}
int stackSize = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
rcSpan* topSpan = solid.spans[x + y*w];
if (!topSpan)
continue;
while (topSpan->next)
topSpan = topSpan->next;
// If the span is not walkable, skip it.
if ((topSpan->flags & RC_WALKABLE) == 0)
continue;
// If the span has been visited already, skip it.
if (topSpan->flags & RC_REACHABLE)
continue;
// Start flood fill.
topSpan->flags |= RC_REACHABLE;
stackSize = 0;
stack[stackSize].set(x, y, topSpan);
stackSize++;
while (stackSize)
{
// Pop a seed from the stack.
stackSize--;
rcReachableSeed cur = stack[stackSize];
const int bot = (int)cur.s->smax;
const int top = cur.s->next ? (int)cur.s->next->smin : MAX_HEIGHT;
// Visit neighbours in all 4 directions.
for (int dir = 0; dir < 4; ++dir)
{
int dx = (int)cur.x + rcGetDirOffsetX(dir);
int dy = (int)cur.y + rcGetDirOffsetY(dir);
// Skip neighbour which are out of bounds.
if (dx < 0 || dy < 0 || dx >= w || dy >= h)
continue;
for (rcSpan* ns = solid.spans[dx + dy*w]; ns; ns = ns->next)
{
// Skip neighbour if it is not walkable.
if ((ns->flags & RC_WALKABLE) == 0)
continue;
// Skip the neighbour if it has been visited already.
if (ns->flags & RC_REACHABLE)
continue;
const int nbot = (int)ns->smax;
const int ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT;
// Skip neightbour if the gap between the spans is too small.
if (rcMin(top,ntop) - rcMax(bot,nbot) < walkableHeight)
continue;
// Skip neightbour if the climb height to the neighbour is too high.
if (rcAbs(nbot - bot) >= walkableClimb)
continue;
// This neighbour has not been visited yet.
// Mark it as reachable and add it to the seed stack.
ns->flags |= RC_REACHABLE;
if (stackSize < MAX_SEEDS)
{
stack[stackSize].set(dx, dy, ns);
stackSize++;
}
}
}
}
}
}
delete [] stack;
rcTimeVal endTime = rcGetPerformanceTimer();
// if (rcGetLog())
// rcGetLog()->log(RC_LOG_PROGRESS, "Mark reachable: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
if (rcGetBuildTimes())
rcGetBuildTimes()->filterMarkReachable += rcGetDeltaTimeUsec(startTime, endTime);
return true;
ctx->stopTimer(RC_TIMER_FILTER_WALKABLE);
}

View File

@@ -0,0 +1,620 @@
//
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//
#include <float.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "Recast.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"
static const int RC_MAX_LAYERS = RC_NOT_CONNECTED;
static const int RC_MAX_NEIS = 16;
struct rcLayerRegion
{
unsigned char layers[RC_MAX_LAYERS];
unsigned char neis[RC_MAX_NEIS];
unsigned short ymin, ymax;
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.
};
static void addUnique(unsigned char* a, unsigned char& an, unsigned char v)
{
const int n = (int)an;
for (int i = 0; i < n; ++i)
if (a[i] == v)
return;
a[an] = v;
an++;
}
static bool contains(const unsigned char* a, const unsigned char an, const unsigned char v)
{
const int n = (int)an;
for (int i = 0; i < n; ++i)
if (a[i] == v)
return true;
return false;
}
inline bool overlapRange(const unsigned short amin, const unsigned short amax,
const unsigned short bmin, const unsigned short bmax)
{
return (amin > bmax || amax < bmin) ? false : true;
}
struct rcLayerSweepSpan
{
unsigned short ns; // number samples
unsigned char id; // region id
unsigned char nei; // neighbour id
};
/// @par
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig
bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf,
const int borderSize, const int walkableHeight,
rcHeightfieldLayerSet& lset)
{
rcAssert(ctx);
ctx->startTimer(RC_TIMER_BUILD_LAYERS);
const int w = chf.width;
const int h = chf.height;
rcScopedDelete<unsigned char> 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);
return false;
}
memset(srcReg,0xff,sizeof(unsigned char)*chf.spanCount);
const int nsweeps = chf.width;
rcScopedDelete<rcLayerSweepSpan> sweeps = (rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP);
if (!sweeps)
{
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps);
return false;
}
// Partition walkable area into monotone regions.
int prevCount[256];
unsigned char regId = 0;
for (int y = borderSize; y < h-borderSize; ++y)
{
memset(prevCount,0,sizeof(int)*regId);
unsigned char sweepId = 0;
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;
unsigned char sid = 0xff;
// -x
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 (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
sid = srcReg[ai];
}
if (sid == 0xff)
{
sid = sweepId++;
sweeps[sid].nei = 0xff;
sweeps[sid].ns = 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);
const unsigned char nr = srcReg[ai];
if (nr != 0xff)
{
// Set neighbour when first valid neighbour is encoutered.
if (sweeps[sid].ns == 0)
sweeps[sid].nei = nr;
if (sweeps[sid].nei == nr)
{
// Update existing neighbour
sweeps[sid].ns++;
prevCount[nr]++;
}
else
{
// This is hit if there is nore than one neighbour.
// Invalidate the neighbour.
sweeps[sid].nei = 0xff;
}
}
}
srcReg[i] = sid;
}
}
// Create unique ID.
for (int i = 0; i < sweepId; ++i)
{
// If the neighbour is set and there is only one continuous connection to it,
// the sweep will be merged with the previous one, else new region is created.
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == (int)sweeps[i].ns)
{
sweeps[i].id = sweeps[i].nei;
}
else
{
if (regId == 255)
{
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow.");
return false;
}
sweeps[i].id = regId++;
}
}
// Remap local sweep ids to region 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] != 0xff)
srcReg[i] = sweeps[srcReg[i]].id;
}
}
}
// Allocate and init layer regions.
const int nregs = (int)regId;
rcScopedDelete<rcLayerRegion> regs = (rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP);
if (!regs)
{
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs);
return false;
}
memset(regs, 0, sizeof(rcLayerRegion)*nregs);
for (int i = 0; i < nregs; ++i)
{
regs[i].layerId = 0xff;
regs[i].ymin = 0xffff;
regs[i].ymax = 0;
}
// Find region neighbours and overlapping regions.
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
const rcCompactCell& c = chf.cells[x+y*w];
unsigned char lregs[RC_MAX_LAYERS];
int nlregs = 0;
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
const rcCompactSpan& s = chf.spans[i];
const unsigned char ri = srcReg[i];
if (ri == 0xff) continue;
regs[ri].ymin = rcMin(regs[ri].ymin, s.y);
regs[ri].ymax = rcMax(regs[ri].ymax, s.y);
// Collect all region layers.
if (nlregs < RC_MAX_LAYERS)
lregs[nlregs++] = 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 char rai = srcReg[ai];
if (rai != 0xff && rai != ri)
addUnique(regs[ri].neis, regs[ri].nneis, rai);
}
}
}
// Update overlapping regions.
for (int i = 0; i < nlregs-1; ++i)
{
for (int j = i+1; j < nlregs; ++j)
{
if (lregs[i] != lregs[j])
{
rcLayerRegion& ri = regs[lregs[i]];
rcLayerRegion& rj = regs[lregs[j]];
addUnique(ri.layers, ri.nlayers, lregs[j]);
addUnique(rj.layers, rj.nlayers, lregs[i]);
}
}
}
}
}
// Create 2D layers from regions.
unsigned char layerId = 0;
static const int MAX_STACK = 64;
unsigned char stack[MAX_STACK];
int nstack = 0;
for (int i = 0; i < nregs; ++i)
{
rcLayerRegion& root = regs[i];
// Skip alreadu visited.
if (root.layerId != 0xff)
continue;
// Start search.
root.layerId = layerId;
root.base = 1;
nstack = 0;
stack[nstack++] = (unsigned char)i;
while (nstack)
{
// Pop front
rcLayerRegion& reg = regs[stack[0]];
nstack--;
for (int j = 0; j < nstack; ++j)
stack[j] = stack[j+1];
const int nneis = (int)reg.nneis;
for (int j = 0; j < nneis; ++j)
{
const unsigned char nei = reg.neis[j];
rcLayerRegion& regn = regs[nei];
// Skip already visited.
if (regn.layerId != 0xff)
continue;
// Skip if the neighbour is overlapping root region.
if (contains(root.layers, root.nlayers, nei))
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);
if ((ymax - ymin) >= 255)
continue;
if (nstack < MAX_STACK)
{
// Deepen
stack[nstack++] = (unsigned char)nei;
// Mark layer id
regn.layerId = layerId;
// Merge current layers to root.
for (int k = 0; k < regn.nlayers; ++k)
addUnique(root.layers, root.nlayers, regn.layers[k]);
root.ymin = rcMin(root.ymin, regn.ymin);
root.ymax = rcMax(root.ymax, regn.ymax);
}
}
}
layerId++;
}
// Merge non-overlapping regions that are close in height.
const unsigned short mergeHeight = (unsigned short)walkableHeight * 4;
for (int i = 0; i < nregs; ++i)
{
rcLayerRegion& ri = regs[i];
if (!ri.base) continue;
unsigned char newId = ri.layerId;
for (;;)
{
unsigned char oldId = 0xff;
for (int j = 0; j < nregs; ++j)
{
if (i == j) continue;
rcLayerRegion& rj = regs[j];
if (!rj.base) continue;
// Skip if teh 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);
if ((ymax - ymin) >= 255)
continue;
// Make sure that there is no overlap when mergin 'ri' and 'rj'.
bool overlap = false;
// Iterate over all regions which have the same layerId as 'rj'
for (int k = 0; k < nregs; ++k)
{
if (regs[k].layerId != rj.layerId)
continue;
// Check if region 'k' is overlapping region 'ri'
// Index to 'regs' is the same as region id.
if (contains(ri.layers,ri.nlayers, (unsigned char)k))
{
overlap = true;
break;
}
}
// Cannot merge of regions overlap.
if (overlap)
continue;
// Can merge i and j.
oldId = rj.layerId;
break;
}
// Could not find anything to merge with, stop.
if (oldId == 0xff)
break;
// Merge
for (int j = 0; j < nregs; ++j)
{
rcLayerRegion& rj = regs[j];
if (rj.layerId == oldId)
{
rj.base = 0;
// Remap layerIds.
rj.layerId = newId;
// 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.
ri.ymin = rcMin(ri.ymin, rj.ymin);
ri.ymax = rcMax(ri.ymax, rj.ymax);
}
}
}
}
// Compact layerIds
unsigned char remap[256];
memset(remap, 0, 256);
// Find number of unique layers.
layerId = 0;
for (int i = 0; i < nregs; ++i)
remap[regs[i].layerId] = 1;
for (int i = 0; i < 256; ++i)
{
if (remap[i])
remap[i] = layerId++;
else
remap[i] = 0xff;
}
// Remap ids.
for (int i = 0; i < nregs; ++i)
regs[i].layerId = remap[regs[i].layerId];
// No layers, return empty.
if (layerId == 0)
{
ctx->stopTimer(RC_TIMER_BUILD_LAYERS);
return true;
}
// Create layers.
rcAssert(lset.layers == 0);
const int lw = w - borderSize*2;
const int lh = h - borderSize*2;
// Build contracted bbox for layers.
float bmin[3], bmax[3];
rcVcopy(bmin, chf.bmin);
rcVcopy(bmax, chf.bmax);
bmin[0] += borderSize*chf.cs;
bmin[2] += borderSize*chf.cs;
bmax[0] -= borderSize*chf.cs;
bmax[2] -= borderSize*chf.cs;
lset.nlayers = (int)layerId;
lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM);
if (!lset.layers)
{
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers);
return false;
}
memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers);
// Store layers.
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;
layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
if (!layer->heights)
{
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize);
return false;
}
memset(layer->heights, 0xff, gridSize);
layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
if (!layer->areas)
{
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize);
return false;
}
memset(layer->areas, 0, gridSize);
layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
if (!layer->cons)
{
ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize);
return false;
}
memset(layer->cons, 0, gridSize);
// Find layer height bounds.
int hmin = 0, hmax = 0;
for (int j = 0; j < nregs; ++j)
{
if (regs[j].base && regs[j].layerId == curId)
{
hmin = (int)regs[j].ymin;
hmax = (int)regs[j].ymax;
}
}
layer->width = lw;
layer->height = lh;
layer->cs = chf.cs;
layer->ch = chf.ch;
// Adjust the bbox to fit the heighfield.
rcVcopy(layer->bmin, bmin);
rcVcopy(layer->bmax, bmax);
layer->bmin[1] = bmin[1] + hmin*chf.ch;
layer->bmax[1] = bmin[1] + hmax*chf.ch;
layer->hmin = hmin;
layer->hmax = hmax;
// Update usable data region.
layer->minx = layer->width;
layer->maxx = 0;
layer->miny = layer->height;
layer->maxy = 0;
// Copy height and area from compact heighfield.
for (int y = 0; y < lh; ++y)
{
for (int x = 0; x < lw; ++x)
{
const int cx = borderSize+x;
const int cy = borderSize+y;
const rcCompactCell& c = chf.cells[cx+cy*w];
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
const rcCompactSpan& s = chf.spans[i];
// Skip unassigned regions.
if (srcReg[i] == 0xff)
continue;
// Skip of does nto belong to current layer.
unsigned char lid = regs[srcReg[i]].layerId;
if (lid != curId)
continue;
// Update data bounds.
layer->minx = rcMin(layer->minx, x);
layer->maxx = rcMax(layer->maxx, x);
layer->miny = rcMin(layer->miny, y);
layer->maxy = rcMax(layer->maxy, y);
// Store height and area type.
const int idx = x+y*lw;
layer->heights[idx] = (unsigned char)(s.y - hmin);
layer->areas[idx] = chf.areas[i];
// Check connection.
unsigned char portal = 0;
unsigned char con = 0;
for (int dir = 0; dir < 4; ++dir)
{
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
{
const int ax = cx + rcGetDirOffsetX(dir);
const int ay = cy + rcGetDirOffsetY(dir);
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
unsigned char alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff;
// Portal mask
if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
{
portal |= (unsigned char)(1<<dir);
// Update height so that it matches on both sides of the portal.
const rcCompactSpan& as = chf.spans[ai];
if (as.y > hmin)
layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin));
}
// Valid connection mask
if (chf.areas[ai] != RC_NULL_AREA && lid == alid)
{
const int nx = ax - borderSize;
const int ny = ay - borderSize;
if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
con |= (unsigned char)(1<<dir);
}
}
}
layer->cons[idx] = (portal << 4) | con;
}
}
}
if (layer->minx > layer->maxx)
layer->minx = layer->maxx = 0;
if (layer->miny > layer->maxy)
layer->miny = layer->maxy = 0;
}
ctx->stopTimer(RC_TIMER_BUILD_LAYERS);
return true;
}

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2009 Mikko Mononen memon@inside.org
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
@@ -21,9 +21,8 @@
#include <string.h>
#include <stdio.h>
#include "Recast.h"
#include "RecastLog.h"
#include "RecastTimer.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"
struct rcEdge
{
@@ -32,36 +31,37 @@ struct rcEdge
unsigned short poly[2];
};
/*static */bool buildMeshAdjacency(unsigned short* polys, const int npolys,
/*static*/ bool buildMeshAdjacency(unsigned short* polys, const int npolys,
const int nverts, const int vertsPerPoly)
{
// Based on code by Eric Lengyel from:
// http://www.terathon.com/code/edges.php
int maxEdgeCount = npolys*vertsPerPoly;
unsigned short* firstEdge = new unsigned short[nverts + maxEdgeCount];
unsigned short* firstEdge = (unsigned short*)rcAlloc(sizeof(unsigned short)*(nverts + maxEdgeCount), RC_ALLOC_TEMP);
if (!firstEdge)
return false;
unsigned short* nextEdge = firstEdge + nverts;
int edgeCount = 0;
rcEdge* edges = new rcEdge[maxEdgeCount];
rcEdge* edges = (rcEdge*)rcAlloc(sizeof(rcEdge)*maxEdgeCount, RC_ALLOC_TEMP);
if (!edges)
{
rcFree(firstEdge);
return false;
}
for (int i = 0; i < nverts; i++)
firstEdge[i] = 0xffff;
// Invalida indices are marked as 0xffff, the following code
// handles them just fine.
firstEdge[i] = RC_MESH_NULL_IDX;
for (int i = 0; i < npolys; ++i)
{
unsigned short* t = &polys[i*vertsPerPoly*2];
for (int j = 0; j < vertsPerPoly; ++j)
{
if (t[j] == RC_MESH_NULL_IDX) break;
unsigned short v0 = t[j];
unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == 0xffff) ? t[0] : t[j+1];
unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1];
if (v0 < v1)
{
rcEdge& edge = edges[edgeCount];
@@ -73,7 +73,7 @@ struct rcEdge
edge.polyEdge[1] = 0;
// Insert edge
nextEdge[edgeCount] = firstEdge[v0];
firstEdge[v0] = edgeCount;
firstEdge[v0] = (unsigned short)edgeCount;
edgeCount++;
}
}
@@ -84,11 +84,12 @@ struct rcEdge
unsigned short* t = &polys[i*vertsPerPoly*2];
for (int j = 0; j < vertsPerPoly; ++j)
{
if (t[j] == RC_MESH_NULL_IDX) break;
unsigned short v0 = t[j];
unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == 0xffff) ? t[0] : t[j+1];
unsigned short v1 = (j+1 >= vertsPerPoly || t[j+1] == RC_MESH_NULL_IDX) ? t[0] : t[j+1];
if (v0 > v1)
{
for (unsigned short e = firstEdge[v1]; e != 0xffff; e = nextEdge[e])
for (unsigned short e = firstEdge[v1]; e != RC_MESH_NULL_IDX; e = nextEdge[e])
{
rcEdge& edge = edges[e];
if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1])
@@ -115,8 +116,8 @@ struct rcEdge
}
}
delete [] firstEdge;
delete [] edges;
rcFree(firstEdge);
rcFree(edges);
return true;
}
@@ -133,8 +134,8 @@ inline int computeVertexHash(int x, int y, int z)
return (int)(n & (VERTEX_BUCKET_COUNT-1));
}
static int addVertex(unsigned short x, unsigned short y, unsigned short z,
unsigned short* verts, int* firstVert, int* nextVert, int& nv)
static unsigned short addVertex(unsigned short x, unsigned short y, unsigned short z,
unsigned short* verts, int* firstVert, int* nextVert, int& nv)
{
int bucket = computeVertexHash(x, 0, z);
int i = firstVert[bucket];
@@ -143,7 +144,7 @@ static int addVertex(unsigned short x, unsigned short y, unsigned short z,
{
const unsigned short* v = &verts[i*3];
if (v[0] == x && (rcAbs(v[1] - y) <= 2) && v[2] == z)
return i;
return (unsigned short)i;
i = nextVert[i]; // next
}
@@ -156,7 +157,7 @@ static int addVertex(unsigned short x, unsigned short y, unsigned short z,
nextVert[i] = firstVert[bucket];
firstVert[bucket] = i;
return i;
return (unsigned short)i;
}
inline int prev(int i, int n) { return i-1 >= 0 ? i-1 : n-1; }
@@ -196,7 +197,7 @@ inline bool collinear(const int* a, const int* b, const int* c)
// 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.
bool intersectProp(const int* a, const int* b, const int* c, const int* d)
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) ||
@@ -287,7 +288,7 @@ 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);
}
int triangulate(int n, const int* verts, int* indices, int* tris)
static int triangulate(int n, const int* verts, int* indices, int* tris)
{
int ntris = 0;
int* dst = tris;
@@ -328,8 +329,6 @@ int triangulate(int n, const int* verts, int* indices, int* tris)
if (mini == -1)
{
// Should not happen.
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "triangulate: Failed to triangulate polygon.");
/* printf("mini == -1 ntris=%d n=%d\n", ntris, n);
for (int i = 0; i < n; i++)
{
@@ -379,7 +378,7 @@ int triangulate(int n, const int* verts, int* indices, int* tris)
static int countPolyVerts(const unsigned short* p, const int nvp)
{
for (int i = 0; i < nvp; ++i)
if (p[i] == 0xffff)
if (p[i] == RC_MESH_NULL_IDX)
return i;
return nvp;
}
@@ -454,8 +453,7 @@ static int getPolyMergeValue(unsigned short* pa, unsigned short* pb,
return dx*dx + dy*dy;
}
static void mergePolys(unsigned short* pa, unsigned short* pb,
const unsigned short* verts, int ea, int eb,
static void mergePolys(unsigned short* pa, unsigned short* pb, int ea, int eb,
unsigned short* tmp, const int nvp)
{
const int na = countPolyVerts(pa, nvp);
@@ -474,6 +472,7 @@ static void mergePolys(unsigned short* pa, unsigned short* pb,
memcpy(pa, tmp, sizeof(unsigned short)*nvp);
}
static void pushFront(int v, int* arr, int& an)
{
an++;
@@ -487,59 +486,157 @@ static void pushBack(int v, int* arr, int& an)
an++;
}
static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int maxTris)
static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short rem)
{
unsigned short* tmpPoly;
int ntris;
static const int nvp = mesh.nvp;
int* edges = 0;
int nedges = 0;
int* hole = 0;
int nhole = 0;
int* hreg = 0;
int nhreg = 0;
int* tris = 0;
int* tverts = 0;
int* thole = 0;
unsigned short* polys = 0;
unsigned short* pregs = 0;
int npolys = 0;
const int nvp = mesh.nvp;
// Count number of polygons to remove.
int nrem = 0;
int numRemovedVerts = 0;
int numTouchedVerts = 0;
int numRemainingEdges = 0;
for (int i = 0; i < mesh.npolys; ++i)
{
unsigned short* p = &mesh.polys[i*nvp*2];
for (int j = 0; j < nvp; ++j)
if (p[j] == rem) { nrem++; break; }
}
edges = new int[nrem*nvp*3];
if (!edges)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'edges' (%d).", nrem*nvp*3);
goto failure;
}
hole = new int[nrem*nvp];
if (!hole)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hole' (%d).", nrem*nvp);
goto failure;
}
hreg = new int[nrem*nvp];
if (!hreg)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'hreg' (%d).", nrem*nvp);
goto failure;
const int nv = countPolyVerts(p, nvp);
int numRemoved = 0;
int numVerts = 0;
for (int j = 0; j < nv; ++j)
{
if (p[j] == rem)
{
numTouchedVerts++;
numRemoved++;
}
numVerts++;
}
if (numRemoved)
{
numRemovedVerts += numRemoved;
numRemainingEdges += numVerts-(numRemoved+1);
}
}
// There would be too few edges remaining to create a polygon.
// This can happen for example when a tip of a triangle is marked
// as deletion, but there are no other polys that share the vertex.
// In this case, the vertex should not be removed.
if (numRemainingEdges <= 2)
return false;
// Find edges which share the removed vertex.
const int maxEdges = numTouchedVerts*2;
int nedges = 0;
rcScopedDelete<int> 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);
return false;
}
for (int i = 0; i < mesh.npolys; ++i)
{
unsigned short* p = &mesh.polys[i*nvp*2];
const int nv = countPolyVerts(p, nvp);
// Collect edges which touches the removed vertex.
for (int j = 0, k = nv-1; j < nv; k = j++)
{
if (p[j] == rem || p[k] == rem)
{
// Arrange edge so that a=rem.
int a = p[j], b = p[k];
if (b == rem)
rcSwap(a,b);
// Check if the edge exists
bool exists = false;
for (int k = 0; k < nedges; ++k)
{
int* e = &edges[k*3];
if (e[1] == b)
{
// Exists, increment vertex share count.
e[2]++;
exists = true;
}
}
// Add new edge.
if (!exists)
{
int* e = &edges[nedges*3];
e[0] = a;
e[1] = b;
e[2] = 1;
nedges++;
}
}
}
}
// There should be no more than 2 open edges.
// This catches the case that two non-adjacent polygons
// share the removed vertex. In that case, do not remove the vertex.
int numOpenEdges = 0;
for (int i = 0; i < nedges; ++i)
{
if (edges[i*3+2] < 2)
numOpenEdges++;
}
if (numOpenEdges > 2)
return false;
return true;
}
static bool removeVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short rem, const int maxTris)
{
const int nvp = mesh.nvp;
// Count number of polygons to remove.
int numRemovedVerts = 0;
for (int i = 0; i < mesh.npolys; ++i)
{
unsigned short* p = &mesh.polys[i*nvp*2];
const int nv = countPolyVerts(p, nvp);
for (int j = 0; j < nv; ++j)
{
if (p[j] == rem)
numRemovedVerts++;
}
}
int nedges = 0;
rcScopedDelete<int> 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);
return false;
}
int nhole = 0;
rcScopedDelete<int> 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<int> 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);
return false;
}
int nharea = 0;
rcScopedDelete<int> 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);
return false;
}
for (int i = 0; i < mesh.npolys; ++i)
{
unsigned short* p = &mesh.polys[i*nvp*2];
@@ -554,17 +651,20 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
{
if (p[j] != rem && p[k] != rem)
{
int* e = &edges[nedges*3];
int* e = &edges[nedges*4];
e[0] = p[k];
e[1] = p[j];
e[2] = mesh.regs[i];
e[3] = mesh.areas[i];
nedges++;
}
}
// Remove the polygon.
unsigned short* p2 = &mesh.polys[(mesh.npolys-1)*nvp*2];
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];
mesh.npolys--;
--i;
}
@@ -589,16 +689,18 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
}
for (int i = 0; i < nedges; ++i)
{
if (edges[i*3+0] > rem) edges[i*3+0]--;
if (edges[i*3+1] > rem) edges[i*3+1]--;
if (edges[i*4+0] > rem) edges[i*4+0]--;
if (edges[i*4+1] > rem) edges[i*4+1]--;
}
if (nedges == 0)
return true;
hole[nhole] = edges[0];
hreg[nhole] = edges[2];
nhole++;
// Start with one vertex, keep appending connected
// segments to the start and end of the hole.
pushBack(edges[0], hole, nhole);
pushBack(edges[2], hreg, nhreg);
pushBack(edges[3], harea, nharea);
while (nedges)
{
@@ -606,28 +708,34 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
for (int i = 0; i < nedges; ++i)
{
const int ea = edges[i*3+0];
const int eb = edges[i*3+1];
const int r = edges[i*3+2];
const int ea = edges[i*4+0];
const int eb = edges[i*4+1];
const int r = edges[i*4+2];
const int a = edges[i*4+3];
bool add = false;
if (hole[0] == eb)
{
// The segment matches the beginning of the hole boundary.
pushFront(ea, hole, nhole);
pushFront(r, hreg, nhreg);
pushFront(a, harea, nharea);
add = true;
}
else if (hole[nhole-1] == ea)
{
// The segment matches the end of the hole boundary.
pushBack(eb, hole, nhole);
pushBack(r, hreg, nhreg);
pushBack(a, harea, nharea);
add = true;
}
if (add)
{
// Remove edge.
edges[i*3+0] = edges[(nedges-1)*3+0];
edges[i*3+1] = edges[(nedges-1)*3+1];
edges[i*3+2] = edges[(nedges-1)*3+2];
// The edge segment was added, remove it.
edges[i*4+0] = edges[(nedges-1)*4+0];
edges[i*4+1] = edges[(nedges-1)*4+1];
edges[i*4+2] = edges[(nedges-1)*4+2];
edges[i*4+3] = edges[(nedges-1)*4+3];
--nedges;
match = true;
--i;
@@ -638,28 +746,25 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
break;
}
tris = new int[nhole*3];
rcScopedDelete<int> tris = (int*)rcAlloc(sizeof(int)*nhole*3, RC_ALLOC_TEMP);
if (!tris)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tris' (%d).", nhole*3);
goto failure;
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tris' (%d).", nhole*3);
return false;
}
tverts = new int[nhole*4];
rcScopedDelete<int> tverts = (int*)rcAlloc(sizeof(int)*nhole*4, RC_ALLOC_TEMP);
if (!tverts)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' (%d).", nhole*4);
goto failure;
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' (%d).", nhole*4);
return false;
}
thole = new int[nhole];
rcScopedDelete<int> thole = (int*)rcAlloc(sizeof(int)*nhole, RC_ALLOC_TEMP);
if (!tverts)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'thole' (%d).", nhole);
goto failure;
ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'thole' (%d).", nhole);
return false;
}
// Generate temp vertex array for triangulation.
@@ -674,27 +779,37 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
}
// Triangulate the hole.
ntris = triangulate(nhole, &tverts[0], &thole[0], tris);
// Merge the hole triangles back to polygons.
polys = new unsigned short[(ntris+1)*nvp];
if (!polys)
int ntris = triangulate(nhole, &tverts[0], &thole[0], tris);
if (ntris < 0)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'polys' (%d).", (ntris+1)*nvp);
goto failure;
}
pregs = new unsigned short[ntris];
if (!pregs)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_WARNING, "removeVertex: Out of memory 'pregs' (%d).", ntris);
goto failure;
ntris = -ntris;
ctx->log(RC_LOG_WARNING, "removeVertex: triangulate() returned bad results.");
}
tmpPoly = &polys[ntris*nvp];
// Merge the hole triangles back to polygons.
rcScopedDelete<unsigned short> 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<unsigned short> 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<unsigned char> pareas = (unsigned char*)rcAlloc(sizeof(unsigned char)*ntris, RC_ALLOC_TEMP);
if (!pregs)
{
ctx->log(RC_LOG_ERROR, "removeVertex: Out of memory 'pareas' (%d).", ntris);
return false;
}
unsigned short* tmpPoly = &polys[ntris*nvp];
// Build initial polygons.
int npolys = 0;
memset(polys, 0xff, ntris*nvp*sizeof(unsigned short));
for (int j = 0; j < ntris; ++j)
{
@@ -704,7 +819,8 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
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] = hreg[t[0]];
pregs[npolys] = (unsigned short)hreg[t[0]];
pareas[npolys] = (unsigned char)harea[t[0]];
npolys++;
}
}
@@ -714,11 +830,11 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
// Merge polygons.
if (nvp > 3)
{
while (true)
for (;;)
{
// Find best polygons to merge.
int bestMergeVal = 0;
int bestPa, bestPb, bestEa, bestEb;
int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
for (int j = 0; j < npolys-1; ++j)
{
@@ -744,9 +860,10 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
// Found best, merge.
unsigned short* pa = &polys[bestPa*nvp];
unsigned short* pb = &polys[bestPb*nvp];
mergePolys(pa, pb, mesh.verts, bestEa, bestEb, tmpPoly, nvp);
mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp);
memcpy(pb, &polys[(npolys-1)*nvp], sizeof(unsigned short)*nvp);
pregs[bestPb] = pregs[npolys-1];
pareas[bestPb] = pareas[npolys-1];
npolys--;
}
else
@@ -766,50 +883,43 @@ static bool removeVertex(rcPolyMesh& mesh, const unsigned short rem, const int m
for (int j = 0; j < nvp; ++j)
p[j] = polys[i*nvp+j];
mesh.regs[mesh.npolys] = pregs[i];
mesh.areas[mesh.npolys] = pareas[i];
mesh.npolys++;
if (mesh.npolys > maxTris)
{
ctx->log(RC_LOG_ERROR, "removeVertex: Too many polygons %d (max:%d).", mesh.npolys, maxTris);
return false;
}
}
delete [] edges;
delete [] hole;
delete [] hreg;
delete [] tris;
delete [] thole;
delete [] tverts;
delete [] polys;
delete [] pregs;
return true;
failure:
delete [] edges;
delete [] hole;
delete [] hreg;
delete [] tris;
delete [] thole;
delete [] tverts;
delete [] polys;
delete [] pregs;
return false;
}
bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh)
/// @par
///
/// @note If the mesh data is to be used to construct a Detour navigation mesh, then the upper
/// limit must be retricted to <= #DT_VERTS_PER_POLYGON.
///
/// @see rcAllocPolyMesh, rcContourSet, rcPolyMesh, rcConfig
bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, const int nvp, rcPolyMesh& mesh)
{
unsigned short* tmpPoly;
rcTimeVal startTime = rcGetPerformanceTimer();
rcTimeVal endTime;
rcAssert(ctx);
ctx->startTimer(RC_TIMER_BUILD_POLYMESH);
vcopy(mesh.bmin, cset.bmin);
vcopy(mesh.bmax, cset.bmax);
rcVcopy(mesh.bmin, cset.bmin);
rcVcopy(mesh.bmax, cset.bmax);
mesh.cs = cset.cs;
mesh.ch = cset.ch;
mesh.borderSize = cset.borderSize;
int maxVertices = 0;
int maxTris = 0;
int maxVertsPerCont = 0;
for (int i = 0; i < cset.nconts; ++i)
{
// Skip null contours.
if (cset.conts[i].nverts < 3) continue;
maxVertices += cset.conts[i].nverts;
maxTris += cset.conts[i].nverts - 2;
maxVertsPerCont = rcMax(maxVertsPerCont, cset.conts[i].nverts);
@@ -817,103 +927,95 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh)
if (maxVertices >= 0xfffe)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many vertices %d.", maxVertices);
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many vertices %d.", maxVertices);
return false;
}
unsigned char* vflags = 0;
int* nextVert = 0;
int* firstVert = 0;
int* indices = 0;
int* tris = 0;
unsigned short* polys = 0;
vflags = new unsigned char[maxVertices];
rcScopedDelete<unsigned char> vflags = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxVertices, RC_ALLOC_TEMP);
if (!vflags)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices);
return false;
}
memset(vflags, 0, maxVertices);
mesh.verts = new unsigned short[maxVertices*3];
mesh.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertices*3, RC_ALLOC_PERM);
if (!mesh.verts)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices);
return false;
}
mesh.polys = new unsigned short[maxTris*nvp*2];
mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris*nvp*2, RC_ALLOC_PERM);
if (!mesh.polys)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.polys' (%d).", maxTris*nvp*2);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.polys' (%d).", maxTris*nvp*2);
return false;
}
mesh.regs = new unsigned short[maxTris];
mesh.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris, RC_ALLOC_PERM);
if (!mesh.regs)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.regs' (%d).", maxTris);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.regs' (%d).", maxTris);
return false;
}
mesh.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris, RC_ALLOC_PERM);
if (!mesh.areas)
{
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.areas' (%d).", maxTris);
return false;
}
mesh.nverts = 0;
mesh.npolys = 0;
mesh.nvp = nvp;
mesh.maxpolys = maxTris;
memset(mesh.verts, 0, sizeof(unsigned short)*maxVertices*3);
memset(mesh.polys, 0xff, sizeof(unsigned short)*maxTris*nvp*2);
memset(mesh.regs, 0, sizeof(unsigned short)*maxTris);
memset(mesh.areas, 0, sizeof(unsigned char)*maxTris);
nextVert = new int[maxVertices];
rcScopedDelete<int> nextVert = (int*)rcAlloc(sizeof(int)*maxVertices, RC_ALLOC_TEMP);
if (!nextVert)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'nextVert' (%d).", maxVertices);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'nextVert' (%d).", maxVertices);
return false;
}
memset(nextVert, 0, sizeof(int)*maxVertices);
firstVert = new int[VERTEX_BUCKET_COUNT];
rcScopedDelete<int> firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP);
if (!firstVert)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT);
return false;
}
for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i)
firstVert[i] = -1;
indices = new int[maxVertsPerCont];
rcScopedDelete<int> indices = (int*)rcAlloc(sizeof(int)*maxVertsPerCont, RC_ALLOC_TEMP);
if (!indices)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'indices' (%d).", maxVertsPerCont);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'indices' (%d).", maxVertsPerCont);
return false;
}
tris = new int[maxVertsPerCont*3];
rcScopedDelete<int> tris = (int*)rcAlloc(sizeof(int)*maxVertsPerCont*3, RC_ALLOC_TEMP);
if (!tris)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'tris' (%d).", maxVertsPerCont*3);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'tris' (%d).", maxVertsPerCont*3);
return false;
}
polys = new unsigned short[(maxVertsPerCont+1)*nvp];
rcScopedDelete<unsigned short> polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*(maxVertsPerCont+1)*nvp, RC_ALLOC_TEMP);
if (!polys)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'polys' (%d).", maxVertsPerCont*nvp);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'polys' (%d).", maxVertsPerCont*nvp);
return false;
}
tmpPoly = &polys[maxVertsPerCont*nvp];
unsigned short* tmpPoly = &polys[maxVertsPerCont*nvp];
for (int i = 0; i < cset.nconts; ++i)
{
rcContour& cont = cset.conts[i];
// Skip empty contours.
// Skip null contours.
if (cont.nverts < 3)
continue;
@@ -925,20 +1027,20 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh)
if (ntris <= 0)
{
// Bad triangulation, should not happen.
/* for (int k = 0; k < cont.nverts; ++k)
/* printf("\tconst float bmin[3] = {%ff,%ff,%ff};\n", cset.bmin[0], cset.bmin[1], cset.bmin[2]);
printf("\tconst float cs = %ff;\n", cset.cs);
printf("\tconst float ch = %ff;\n", cset.ch);
printf("\tconst int verts[] = {\n");
for (int k = 0; k < cont.nverts; ++k)
{
const int* v = &cont.verts[k*4];
printf("\t\t%d,%d,%d,%d,\n", v[0], v[1], v[2], v[3]);
if (nBadPos < 100)
{
badPos[nBadPos*3+0] = v[0];
badPos[nBadPos*3+1] = v[1];
badPos[nBadPos*3+2] = v[2];
nBadPos++;
}
}*/
}
printf("\t};\n\tconst int nverts = sizeof(verts)/(sizeof(int)*4);\n");*/
ctx->log(RC_LOG_WARNING, "rcBuildPolyMesh: Bad triangulation Contour %d.", i);
ntris = -ntris;
}
// Add and merge vertices.
for (int j = 0; j < cont.nverts; ++j)
{
@@ -972,11 +1074,11 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh)
// Merge polygons.
if (nvp > 3)
{
while (true)
for(;;)
{
// Find best polygons to merge.
int bestMergeVal = 0;
int bestPa, bestPb, bestEa, bestEb;
int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0;
for (int j = 0; j < npolys-1; ++j)
{
@@ -1002,7 +1104,7 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh)
// Found best, merge.
unsigned short* pa = &polys[bestPa*nvp];
unsigned short* pb = &polys[bestPb*nvp];
mergePolys(pa, pb, mesh.verts, bestEa, bestEb, tmpPoly, nvp);
mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp);
memcpy(pb, &polys[(npolys-1)*nvp], sizeof(unsigned short)*nvp);
npolys--;
}
@@ -1014,7 +1116,6 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh)
}
}
// Store polygons.
for (int j = 0; j < npolys; ++j)
{
@@ -1023,7 +1124,13 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh)
for (int k = 0; k < nvp; ++k)
p[k] = q[k];
mesh.regs[mesh.npolys] = cont.reg;
mesh.areas[mesh.npolys] = cont.area;
mesh.npolys++;
if (mesh.npolys > maxTris)
{
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many polygons %d (max:%d).", mesh.npolys, maxTris);
return false;
}
}
}
@@ -1033,131 +1140,174 @@ bool rcBuildPolyMesh(rcContourSet& cset, int nvp, rcPolyMesh& mesh)
{
if (vflags[i])
{
if (!removeVertex(mesh, i, maxTris))
goto failure;
for (int j = i; j < mesh.nverts-1; ++j)
if (!canRemoveVertex(ctx, mesh, (unsigned short)i))
continue;
if (!removeVertex(ctx, mesh, (unsigned short)i, maxTris))
{
// Failed to remove vertex
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Failed to remove edge vertex %d.", i);
return false;
}
// Remove vertex
// Note: mesh.nverts is already decremented inside removeVertex()!
for (int j = i; j < mesh.nverts; ++j)
vflags[j] = vflags[j+1];
--i;
}
}
delete [] vflags;
delete [] firstVert;
delete [] nextVert;
delete [] indices;
delete [] tris;
// Calculate adjacency.
if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, nvp))
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMesh: Adjacency failed.");
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Adjacency failed.");
return false;
}
endTime = rcGetPerformanceTimer();
// Find portal edges
if (mesh.borderSize > 0)
{
const int w = cset.width;
const int h = cset.height;
for (int i = 0; i < mesh.npolys; ++i)
{
unsigned short* p = &mesh.polys[i*2*nvp];
for (int j = 0; j < nvp; ++j)
{
if (p[j] == RC_MESH_NULL_IDX) break;
// Skip connected edges.
if (p[nvp+j] != RC_MESH_NULL_IDX)
continue;
int nj = j+1;
if (nj >= nvp || p[nj] == RC_MESH_NULL_IDX) nj = 0;
const unsigned short* va = &mesh.verts[p[j]*3];
const unsigned short* vb = &mesh.verts[p[nj]*3];
if ((int)va[0] == 0 && (int)vb[0] == 0)
p[nvp+j] = 0x8000 | 0;
else if ((int)va[2] == h && (int)vb[2] == h)
p[nvp+j] = 0x8000 | 1;
else if ((int)va[0] == w && (int)vb[0] == w)
p[nvp+j] = 0x8000 | 2;
else if ((int)va[2] == 0 && (int)vb[2] == 0)
p[nvp+j] = 0x8000 | 3;
}
}
}
// Just allocate the mesh flags array. The user is resposible to fill it.
mesh.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*mesh.npolys, RC_ALLOC_PERM);
if (!mesh.flags)
{
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.flags' (%d).", mesh.npolys);
return false;
}
memset(mesh.flags, 0, sizeof(unsigned short) * mesh.npolys);
// if (rcGetLog())
// rcGetLog()->log(RC_LOG_PROGRESS, "Build polymesh: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
if (rcGetBuildTimes())
rcGetBuildTimes()->buildPolymesh += rcGetDeltaTimeUsec(startTime, endTime);
if (mesh.nverts > 0xffff)
{
ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff);
}
if (mesh.npolys > 0xffff)
{
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;
failure:
delete [] vflags;
delete [] tmpPoly;
delete [] firstVert;
delete [] nextVert;
delete [] indices;
delete [] tris;
return false;
}
bool rcMergePolyMeshes(rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh)
/// @see rcAllocPolyMesh, rcPolyMesh
bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh)
{
rcAssert(ctx);
if (!nmeshes || !meshes)
return true;
rcTimeVal startTime = rcGetPerformanceTimer();
rcTimeVal endTime;
int* nextVert = 0;
int* firstVert = 0;
unsigned short* vremap = 0;
ctx->startTimer(RC_TIMER_MERGE_POLYMESH);
mesh.nvp = meshes[0]->nvp;
mesh.cs = meshes[0]->cs;
mesh.ch = meshes[0]->ch;
vcopy(mesh.bmin, meshes[0]->bmin);
vcopy(mesh.bmax, meshes[0]->bmax);
rcVcopy(mesh.bmin, meshes[0]->bmin);
rcVcopy(mesh.bmax, meshes[0]->bmax);
int maxVerts = 0;
int maxPolys = 0;
int maxVertsPerMesh = 0;
for (int i = 0; i < nmeshes; ++i)
{
vmin(mesh.bmin, meshes[i]->bmin);
vmax(mesh.bmax, meshes[i]->bmax);
rcVmin(mesh.bmin, meshes[i]->bmin);
rcVmax(mesh.bmax, meshes[i]->bmax);
maxVertsPerMesh = rcMax(maxVertsPerMesh, meshes[i]->nverts);
maxVerts += meshes[i]->nverts;
maxPolys += meshes[i]->npolys;
}
mesh.nverts = 0;
mesh.verts = new unsigned short[maxVerts*3];
mesh.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVerts*3, RC_ALLOC_PERM);
if (!mesh.verts)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.verts' (%d).", maxVerts*3);
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.verts' (%d).", maxVerts*3);
return false;
}
mesh.npolys = 0;
mesh.polys = new unsigned short[maxPolys*2*mesh.nvp];
mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys*2*mesh.nvp, RC_ALLOC_PERM);
if (!mesh.polys)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.polys' (%d).", maxPolys*2*mesh.nvp);
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.polys' (%d).", maxPolys*2*mesh.nvp);
return false;
}
memset(mesh.polys, 0xff, sizeof(unsigned short)*maxPolys*2*mesh.nvp);
mesh.regs = new unsigned short[maxPolys];
mesh.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys, RC_ALLOC_PERM);
if (!mesh.regs)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.regs' (%d).", maxPolys);
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.regs' (%d).", maxPolys);
return false;
}
memset(mesh.regs, 0, sizeof(unsigned short)*maxPolys);
mesh.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxPolys, RC_ALLOC_PERM);
if (!mesh.areas)
{
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.areas' (%d).", maxPolys);
return false;
}
memset(mesh.areas, 0, sizeof(unsigned char)*maxPolys);
mesh.flags = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys, RC_ALLOC_PERM);
if (!mesh.flags)
{
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.flags' (%d).", maxPolys);
return false;
}
memset(mesh.flags, 0, sizeof(unsigned short)*maxPolys);
nextVert = new int[maxVerts];
rcScopedDelete<int> nextVert = (int*)rcAlloc(sizeof(int)*maxVerts, RC_ALLOC_TEMP);
if (!nextVert)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' (%d).", maxVerts);
goto failure;
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' (%d).", maxVerts);
return false;
}
memset(nextVert, 0, sizeof(int)*maxVerts);
firstVert = new int[VERTEX_BUCKET_COUNT];
rcScopedDelete<int> firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP);
if (!firstVert)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT);
goto failure;
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT);
return false;
}
for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i)
firstVert[i] = -1;
vremap = new unsigned short[maxVertsPerMesh];
rcScopedDelete<unsigned short> vremap = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerMesh, RC_ALLOC_PERM);
if (!vremap)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' (%d).", maxVertsPerMesh);
goto failure;
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' (%d).", maxVertsPerMesh);
return false;
}
memset(nextVert, 0, sizeof(int)*maxVerts);
@@ -1172,7 +1322,7 @@ bool rcMergePolyMeshes(rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh)
{
unsigned short* v = &pmesh->verts[j*3];
vremap[j] = addVertex(v[0]+ox, v[1], v[2]+oz,
mesh.verts, firstVert, nextVert, mesh.nverts);
mesh.verts, firstVert, nextVert, mesh.nverts);
}
for (int j = 0; j < pmesh->npolys; ++j)
@@ -1180,10 +1330,12 @@ bool rcMergePolyMeshes(rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh)
unsigned short* tgt = &mesh.polys[mesh.npolys*2*mesh.nvp];
unsigned short* src = &pmesh->polys[j*2*mesh.nvp];
mesh.regs[mesh.npolys] = pmesh->regs[j];
mesh.areas[mesh.npolys] = pmesh->areas[j];
mesh.flags[mesh.npolys] = pmesh->flags[j];
mesh.npolys++;
for (int k = 0; k < mesh.nvp; ++k)
{
if (src[k] == 0xffff) break;
if (src[k] == RC_MESH_NULL_IDX) break;
tgt[k] = vremap[src[k]];
}
}
@@ -1192,27 +1344,20 @@ bool rcMergePolyMeshes(rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh)
// Calculate adjacency.
if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, mesh.nvp))
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcMergePolyMeshes: Adjacency failed.");
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Adjacency failed.");
return false;
}
delete [] firstVert;
delete [] nextVert;
delete [] vremap;
if (mesh.nverts > 0xffff)
{
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many vertices %d (max %d). Data can be corrupted.", mesh.nverts, 0xffff);
}
if (mesh.npolys > 0xffff)
{
ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: The resulting mesh has too many polygons %d (max %d). Data can be corrupted.", mesh.npolys, 0xffff);
}
endTime = rcGetPerformanceTimer();
if (rcGetBuildTimes())
rcGetBuildTimes()->mergePolyMesh += rcGetDeltaTimeUsec(startTime, endTime);
ctx->stopTimer(RC_TIMER_MERGE_POLYMESH);
return true;
failure:
delete [] firstVert;
delete [] nextVert;
delete [] vremap;
return false;
}

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2009 Mikko Mononen memon@inside.org
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
@@ -23,248 +23,75 @@
#include <stdlib.h>
#include <stdio.h>
#include "Recast.h"
#include "RecastLog.h"
#include "RecastTimer.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"
static const unsigned RC_UNSET_HEIGHT = 0xffff;
struct rcHeightPatch
{
inline rcHeightPatch() : data(0) {}
inline ~rcHeightPatch() { delete [] data; }
inline rcHeightPatch() : data(0), xmin(0), ymin(0), width(0), height(0) {}
inline ~rcHeightPatch() { rcFree(data); }
unsigned short* data;
int xmin, ymin, width, height;
};
static int circumCircle(const float xp, const float yp,
const float x1, const float y1,
const float x2, const float y2,
const float x3, const float y3,
float& xc, float& yc, float& rsqr)
{
static const float EPSILON = 1e-6f;
const float fabsy1y2 = rcAbs(y1-y2);
const float fabsy2y3 = rcAbs(y2-y3);
/* Check for coincident points */
if (fabsy1y2 < EPSILON && fabsy2y3 < EPSILON)
return 0;
if (fabsy1y2 < EPSILON)
{
const float m2 = - (x3-x2) / (y3-y2);
const float mx2 = (x2 + x3) / 2.0f;
const float my2 = (y2 + y3) / 2.0f;
xc = (x2 + x1) / 2.0f;
yc = m2 * (xc - mx2) + my2;
}
else if (fabsy2y3 < EPSILON)
{
const float m1 = - (x2-x1) / (y2-y1);
const float mx1 = (x1 + x2) / 2.0f;
const float my1 = (y1 + y2) / 2.0f;
xc = (x3 + x2) / 2.0f;
yc = m1 * (xc - mx1) + my1;
}
else
{
const float m1 = - (x2-x1) / (y2-y1);
const float m2 = - (x3-x2) / (y3-y2);
const float mx1 = (x1 + x2) / 2.0f;
const float mx2 = (x2 + x3) / 2.0f;
const float my1 = (y1 + y2) / 2.0f;
const float my2 = (y2 + y3) / 2.0f;
xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2);
if (fabsy1y2 > fabsy2y3)
yc = m1 * (xc - mx1) + my1;
else
yc = m2 * (xc - mx2) + my2;
}
float dx,dy;
dx = x2 - xc;
dy = y2 - yc;
rsqr = dx*dx + dy*dy;
dx = xp - xc;
dy = yp - yc;
const float drsqr = dx*dx + dy*dy;
return (drsqr <= rsqr) ? 1 : 0;
}
static float *_qsort_verts;
static int ptcmp(const void *v1, const void *v2)
{
const float* p1 = &_qsort_verts[(*(const int*)v1)*3];
const float* p2 = &_qsort_verts[(*(const int*)v2)*3];
if (p1[0] < p2[0])
return -1;
else if (p1[0] > p2[0])
return 1;
else
return 0;
}
// Based on Paul Bourke's triangulate.c
// http://astronomy.swin.edu.au/~pbourke/terrain/triangulate/triangulate.c
static void delaunay(const int nv, float *verts, rcIntArray& idx, rcIntArray& tris, rcIntArray& edges)
{
// Sort vertices
idx.resize(nv);
for (int i = 0; i < nv; ++i)
idx[i] = i;
_qsort_verts = verts;
qsort(&idx[0], idx.size(), sizeof(int), ptcmp);
// Find the maximum and minimum vertex bounds.
// This is to allow calculation of the bounding triangle
float xmin = verts[0];
float ymin = verts[2];
float xmax = xmin;
float ymax = ymin;
for (int i = 1; i < nv; ++i)
{
xmin = rcMin(xmin, verts[i*3+0]);
xmax = rcMax(xmax, verts[i*3+0]);
ymin = rcMin(ymin, verts[i*3+2]);
ymax = rcMax(ymax, verts[i*3+2]);
}
float dx = xmax - xmin;
float dy = ymax - ymin;
float dmax = (dx > dy) ? dx : dy;
float xmid = (xmax + xmin) / 2.0f;
float ymid = (ymax + ymin) / 2.0f;
// Set up the supertriangle
// This is a triangle which encompasses all the sample points.
// The supertriangle coordinates are added to the end of the
// vertex list. The supertriangle is the first triangle in
// the triangle list.
float sv[3*3];
sv[0] = xmid - 20 * dmax;
sv[1] = 0;
sv[2] = ymid - dmax;
sv[3] = xmid;
sv[4] = 0;
sv[5] = ymid + 20 * dmax;
sv[6] = xmid + 20 * dmax;
sv[7] = 0;
sv[8] = ymid - dmax;
tris.push(-3);
tris.push(-2);
tris.push(-1);
tris.push(0); // not completed
for (int i = 0; i < nv; ++i)
{
const float xp = verts[idx[i]*3+0];
const float yp = verts[idx[i]*3+2];
edges.resize(0);
// Set up the edge buffer.
// If the point (xp,yp) lies inside the circumcircle then the
// three edges of that triangle are added to the edge buffer
// and that triangle is removed.
for (int j = 0; j < tris.size()/4; ++j)
{
int* t = &tris[j*4];
if (t[3]) // completed?
continue;
const float* v1 = t[0] < 0 ? &sv[(t[0]+3)*3] : &verts[idx[t[0]]*3];
const float* v2 = t[1] < 0 ? &sv[(t[1]+3)*3] : &verts[idx[t[1]]*3];
const float* v3 = t[2] < 0 ? &sv[(t[2]+3)*3] : &verts[idx[t[2]]*3];
float xc,yc,rsqr;
int inside = circumCircle(xp,yp, v1[0],v1[2], v2[0],v2[2], v3[0],v3[2], xc,yc,rsqr);
if (xc < xp && rcSqr(xp-xc) > rsqr)
t[3] = 1;
if (inside)
{
// Collect triangle edges.
edges.push(t[0]);
edges.push(t[1]);
edges.push(t[1]);
edges.push(t[2]);
edges.push(t[2]);
edges.push(t[0]);
// Remove triangle j.
t[0] = tris[tris.size()-4];
t[1] = tris[tris.size()-3];
t[2] = tris[tris.size()-2];
t[3] = tris[tris.size()-1];
tris.resize(tris.size()-4);
j--;
}
}
// Remove duplicate edges.
const int ne = edges.size()/2;
for (int j = 0; j < ne-1; ++j)
{
for (int k = j+1; k < ne; ++k)
{
// Dupe?, make null.
if ((edges[j*2+0] == edges[k*2+1]) && (edges[j*2+1] == edges[k*2+0]))
{
edges[j*2+0] = 0;
edges[j*2+1] = 0;
edges[k*2+0] = 0;
edges[k*2+1] = 0;
}
}
}
// Form new triangles for the current point
// Skipping over any null.
// All edges are arranged in clockwise order.
for (int j = 0; j < ne; ++j)
{
if (edges[j*2+0] == edges[j*2+1]) continue;
tris.push(edges[j*2+0]);
tris.push(edges[j*2+1]);
tris.push(i);
tris.push(0); // not completed
}
}
// Remove triangles with supertriangle vertices
// These are triangles which have a vertex number greater than nv
for (int i = 0; i < tris.size()/4; ++i)
{
int* t = &tris[i*4];
if (t[0] < 0 || t[1] < 0 || t[2] < 0)
{
t[0] = tris[tris.size()-4];
t[1] = tris[tris.size()-3];
t[2] = tris[tris.size()-2];
t[3] = tris[tris.size()-1];
tris.resize(tris.size()-4);
i--;
}
}
// Triangle vertices are pointing to sorted vertices, remap indices.
for (int i = 0; i < tris.size(); ++i)
tris[i] = idx[tris[i]];
}
inline float vdot2(const float* a, const float* b)
{
return a[0]*b[0] + a[2]*b[2];
}
inline float vdistSq2(const float* p, const float* q)
{
const float dx = q[0] - p[0];
const float dy = q[2] - p[2];
return dx*dx + dy*dy;
}
inline float vdist2(const float* p, const float* q)
{
return sqrtf(vdistSq2(p,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];
const float v2 = p3[2] - p1[2];
return u1 * v2 - v1 * u2;
}
static bool circumCircle(const float* p1, const float* p2, const float* p3,
float* c, float& r)
{
static const float EPS = 1e-6f;
const float cp = vcross2(p1, p2, p3);
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);
return true;
}
c[0] = p1[0];
c[2] = p1[2];
r = 0;
return false;
}
static float distPtTri(const float* p, const float* a, const float* b, const float* c)
{
float v0[3], v1[3], v2[3];
vsub(v0, c,a);
vsub(v1, b,a);
vsub(v2, p,a);
rcVsub(v0, c,a);
rcVsub(v1, b,a);
rcVsub(v2, p,a);
const float dot00 = vdot2(v0, v0);
const float dot01 = vdot2(v0, v1);
@@ -273,15 +100,15 @@ static float distPtTri(const float* p, const float* a, const float* b, const flo
const float dot12 = vdot2(v1, v2);
// Compute barycentric coordinates
float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
const float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
const float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
// If point lies inside the triangle, return interpolated y-coord.
static const float EPS = 1e-4f;
if (u >= -EPS && v >= -EPS && (u+v) <= 1+EPS)
{
float y = a[1] + v0[1]*u + v1[1]*v;
const float y = a[1] + v0[1]*u + v1[1]*v;
return fabsf(y-p[1]);
}
return FLT_MAX;
@@ -332,7 +159,7 @@ static float distancePtSeg2d(const float* pt, const float* p, const float* q)
return dx*dx + dz*dz;
}
static float distToTriMesh(const float* p, const float* verts, int nverts, const int* tris, int ntris)
static float distToTriMesh(const float* p, const float* verts, const int /*nverts*/, const int* tris, const int ntris)
{
float dmin = FLT_MAX;
for (int i = 0; i < ntris; ++i)
@@ -366,35 +193,335 @@ static float distToPoly(int nvert, const float* verts, const float* p)
}
static unsigned short getHeight(const float* pos, const float* bmin, const float ics, const rcHeightPatch& hp)
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)
{
int ix = (int)floorf((pos[0]-bmin[0])*ics + 0.01f);
int iz = (int)floorf((pos[2]-bmin[2])*ics + 0.01f);
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);
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};
float dmin = FLT_MAX;
for (int i = 0; i < 8; ++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 float d = fabsf(nh*ch - fy);
if (d < dmin)
{
h = nh;
dmin = d;
}
/* 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)
{
h = nh;
dmin = d;
} */
}
}
return h;
}
static bool buildPolyDetail(const float* in, const int nin, unsigned short reg,
enum EdgeValues
{
UNDEF = -1,
HULL = -2,
};
static int findEdge(const int* edges, int nedges, int s, int t)
{
for (int i = 0; i < nedges; i++)
{
const int* e = &edges[i*4];
if ((e[0] == s && e[1] == t) || (e[0] == t && e[1] == s))
return i;
}
return UNDEF;
}
static int addEdge(rcContext* ctx, int* edges, int& nedges, const int maxEdges, int s, int t, int l, int r)
{
if (nedges >= maxEdges)
{
ctx->log(RC_LOG_ERROR, "addEdge: Too many edges (%d/%d).", nedges, maxEdges);
return UNDEF;
}
// Add edge if not already in the triangulation.
int e = findEdge(edges, nedges, s, t);
if (e == UNDEF)
{
int* e = &edges[nedges*4];
e[0] = s;
e[1] = t;
e[2] = l;
e[3] = r;
return nedges++;
}
else
{
return UNDEF;
}
}
static void updateLeftFace(int* e, int s, int t, int f)
{
if (e[0] == s && e[1] == t && e[2] == UNDEF)
e[2] = f;
else if (e[1] == s && e[0] == t && e[3] == UNDEF)
e[3] = f;
}
static int overlapSegSeg2d(const float* a, const float* b, const float* c, const float* d)
{
const float a1 = vcross2(a, b, d);
const float a2 = vcross2(a, b, c);
if (a1*a2 < 0.0f)
{
float a3 = vcross2(c, d, a);
float a4 = a3 + a2 - a1;
if (a3 * a4 < 0.0f)
return 1;
}
return 0;
}
static bool overlapEdges(const float* pts, const int* edges, int nedges, int s1, int t1)
{
for (int i = 0; i < nedges; ++i)
{
const int s0 = edges[i*4+0];
const int t0 = edges[i*4+1];
// Same or connected edges do not overlap.
if (s0 == s1 || s0 == t1 || t0 == s1 || t0 == t1)
continue;
if (overlapSegSeg2d(&pts[s0*3],&pts[t0*3], &pts[s1*3],&pts[t1*3]))
return true;
}
return false;
}
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)
{
s = edge[0];
t = edge[1];
}
else if (edge[3] == UNDEF)
{
s = edge[1];
t = edge[0];
}
else
{
// Edge already completed.
return;
}
// Find best point on left of edge.
int pt = npts;
float c[3] = {0,0,0};
float r = -1;
for (int u = 0; u < npts; ++u)
{
if (u == s || u == t) continue;
if (vcross2(&pts[s*3], &pts[t*3], &pts[u*3]) > EPS)
{
if (r < 0)
{
// The circle is not updated yet, do it now.
pt = u;
circumCircle(&pts[s*3], &pts[t*3], &pts[u*3], c, r);
continue;
}
const float d = vdist2(c, &pts[u*3]);
const float tol = 0.001f;
if (d > r*(1+tol))
{
// Outside current circumcircle, skip.
continue;
}
else if (d < r*(1-tol))
{
// Inside safe circumcircle, update circle.
pt = u;
circumCircle(&pts[s*3], &pts[t*3], &pts[u*3], c, r);
}
else
{
// Inside epsilon circum circle, do extra tests to make sure the edge is valid.
// s-u and t-u cannot overlap with s-pt nor t-pt if they exists.
if (overlapEdges(pts, edges, nedges, s,u))
continue;
if (overlapEdges(pts, edges, nedges, t,u))
continue;
// Edge is valid.
pt = u;
circumCircle(&pts[s*3], &pts[t*3], &pts[u*3], c, r);
}
}
}
// Add new triangle or update edge info if s-t is on hull.
if (pt < npts)
{
// Update face information of edge being completed.
updateLeftFace(&edges[e*4], s, t, nfaces);
// 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);
else
updateLeftFace(&edges[e*4], pt, s, nfaces);
// 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);
else
updateLeftFace(&edges[e*4], t, pt, nfaces);
nfaces++;
}
else
{
updateLeftFace(&edges[e*4], s, t, HULL);
}
}
static void delaunayHull(rcContext* ctx, const int npts, const float* pts,
const int nhull, const int* hull,
rcIntArray& tris, rcIntArray& edges)
{
int nfaces = 0;
int nedges = 0;
const int maxEdges = npts*10;
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);
int currentEdge = 0;
while (currentEdge < nedges)
{
if (edges[currentEdge*4+2] == UNDEF)
completeFacet(ctx, pts, npts, &edges[0], nedges, maxEdges, nfaces, currentEdge);
if (edges[currentEdge*4+3] == 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)
tris[i] = -1;
for (int i = 0; i < nedges; ++i)
{
const int* e = &edges[i*4];
if (e[3] >= 0)
{
// Left face
int* t = &tris[e[3]*4];
if (t[0] == -1)
{
t[0] = e[0];
t[1] = e[1];
}
else if (t[0] == e[1])
t[2] = e[0];
else if (t[1] == e[0])
t[2] = e[1];
}
if (e[2] >= 0)
{
// Right
int* t = &tris[e[2]*4];
if (t[0] == -1)
{
t[0] = e[1];
t[1] = e[0];
}
else if (t[0] == e[0])
t[2] = e[1];
else if (t[1] == e[1])
t[2] = e[0];
}
}
for (int i = 0; i < tris.size()/4; ++i)
{
int* t = &tris[i*4];
if (t[0] == -1 || t[1] == -1 || t[2] == -1)
{
ctx->log(RC_LOG_WARNING, "delaunayHull: Removing dangling face %d [%d,%d,%d].", i, t[0],t[1],t[2]);
t[0] = tris[tris.size()-4];
t[1] = tris[tris.size()-3];
t[2] = tris[tris.size()-2];
t[3] = tris[tris.size()-1];
tris.resize(tris.size()-4);
--i;
}
}
}
inline float getJitterX(const int i)
{
return (((i * 0x8da6b343) & 0xffff) / 65535.0f * 2.0f) - 1.0f;
}
inline float getJitterY(const int i)
{
return (((i * 0xd8163841) & 0xffff) / 65535.0f * 2.0f) - 1.0f;
}
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& idx, rcIntArray& samples)
rcIntArray& edges, rcIntArray& samples)
{
static const int MAX_VERTS = 256;
static const int MAX_EDGE = 64;
float edge[(MAX_EDGE+1)*3];
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).
static const int MAX_VERTS_PER_EDGE = 32;
float edge[(MAX_VERTS_PER_EDGE+1)*3];
int hull[MAX_VERTS];
int nhull = 0;
nverts = 0;
for (int i = 0; i < nin; ++i)
vcopy(&verts[i*3], &in[i*3]);
rcVcopy(&verts[i*3], &in[i*3]);
nverts = nin;
const float ics = 1.0f/chf.cs;
const float cs = chf.cs;
const float ics = 1.0f/cs;
// Tesselate outlines.
// Tessellate outlines.
// This is done in separate pass in order to ensure
// seamless height values across the ply boundaries.
if (sampleDist > 0)
@@ -403,17 +530,24 @@ static bool buildPolyDetail(const float* in, const int nin, unsigned short reg,
{
const float* vj = &in[j*3];
const float* vi = &in[i*3];
bool swapped = false;
// Make sure the segments are always handled in same order
// using lexological sort or else there will be seams.
if (fabsf(vj[0]-vi[0]) < 1e-6f)
{
if (vj[2] > vi[2])
{
rcSwap(vj,vi);
swapped = true;
}
}
else
{
if (vj[0] > vi[0])
{
rcSwap(vj,vi);
swapped = true;
}
}
// Create samples along the edge.
float dx = vi[0] - vj[0];
@@ -421,9 +555,10 @@ static bool buildPolyDetail(const float* in, const int nin, unsigned short reg,
float dz = vi[2] - vj[2];
float d = sqrtf(dx*dx + dz*dz);
int nn = 1 + (int)floorf(d/sampleDist);
if (nn > MAX_EDGE) nn = MAX_EDGE;
if (nn >= MAX_VERTS_PER_EDGE) nn = MAX_VERTS_PER_EDGE-1;
if (nverts+nn >= MAX_VERTS)
nn = MAX_VERTS-1-nverts;
for (int k = 0; k <= nn; ++k)
{
float u = (float)k/(float)nn;
@@ -431,10 +566,10 @@ static bool buildPolyDetail(const float* in, const int nin, unsigned short reg,
pos[0] = vj[0] + dx*u;
pos[1] = vj[1] + dy*u;
pos[2] = vj[2] + dz*u;
pos[1] = chf.bmin[1] + getHeight(pos, chf.bmin, ics, hp)*chf.ch;
pos[1] = getHeight(pos[0],pos[1],pos[2], cs, ics, chf.ch, hp)*chf.ch;
}
// Simplify samples.
int idx[MAX_EDGE] = {0,nn};
int idx[MAX_VERTS_PER_EDGE] = {0,nn};
int nidx = 2;
for (int k = 0; k < nidx-1; )
{
@@ -468,31 +603,61 @@ static bool buildPolyDetail(const float* in, const int nin, unsigned short reg,
++k;
}
}
hull[nhull++] = j;
// Add new vertices.
for (int k = 1; k < nidx-1; ++k)
if (swapped)
{
vcopy(&verts[nverts*3], &edge[idx[k]*3]);
nverts++;
for (int k = nidx-2; k > 0; --k)
{
rcVcopy(&verts[nverts*3], &edge[idx[k]*3]);
hull[nhull++] = nverts;
nverts++;
}
}
else
{
for (int k = 1; k < nidx-1; ++k)
{
rcVcopy(&verts[nverts*3], &edge[idx[k]*3]);
hull[nhull++] = nverts;
nverts++;
}
}
}
}
// Tesselate the base mesh.
// Tessellate the base mesh.
edges.resize(0);
tris.resize(0);
idx.resize(0);
delaunay(nverts, verts, idx, tris, edges);
delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges);
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);
}
return true;
}
if (sampleDist > 0)
{
// Create sample locations in a grid.
float bmin[3], bmax[3];
vcopy(bmin, in);
vcopy(bmax, in);
rcVcopy(bmin, in);
rcVcopy(bmax, in);
for (int i = 1; i < nin; ++i)
{
vmin(bmin, &in[i*3]);
vmax(bmax, &in[i*3]);
rcVmin(bmin, &in[i*3]);
rcVmax(bmax, &in[i*3]);
}
int x0 = (int)floorf(bmin[0]/sampleDist);
int x1 = (int)ceilf(bmax[0]/sampleDist);
@@ -505,56 +670,71 @@ static bool buildPolyDetail(const float* in, const int nin, unsigned short reg,
{
float pt[3];
pt[0] = x*sampleDist;
pt[1] = (bmax[1]+bmin[1])*0.5f;
pt[2] = z*sampleDist;
// 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, chf.bmin, ics, hp));
samples.push(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, 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.
const int nsamples = samples.size()/3;
const int nsamples = samples.size()/4;
for (int iter = 0; iter < nsamples; ++iter)
{
if (nverts >= MAX_VERTS)
break;
// Find sample with most error.
float bestpt[3];
float bestpt[3] = {0,0,0};
float bestd = 0;
int besti = -1;
for (int i = 0; i < nsamples; ++i)
{
const int* s = &samples[i*4];
if (s[3]) continue; // skip added.
float pt[3];
pt[0] = samples[i*3+0]*sampleDist;
pt[1] = chf.bmin[1] + samples[i*3+1]*chf.ch;
pt[2] = samples[i*3+2]*sampleDist;
// The sample location is jittered to get rid of some bad triangulations
// which are cause by symmetrical data from the grid structure.
pt[0] = s[0]*sampleDist + getJitterX(i)*cs*0.1f;
pt[1] = s[1]*chf.ch;
pt[2] = s[2]*sampleDist + getJitterY(i)*cs*0.1f;
float d = distToTriMesh(pt, verts, nverts, &tris[0], tris.size()/4);
if (d < 0) continue; // did not hit the mesh.
if (d > bestd)
{
bestd = d;
vcopy(bestpt,pt);
besti = i;
rcVcopy(bestpt,pt);
}
}
// If the max error is within accepted threshold, stop tesselating.
if (bestd <= sampleMaxError)
if (bestd <= sampleMaxError || besti == -1)
break;
// Mark sample as added.
samples[besti*4+3] = 1;
// Add the new sample point.
vcopy(&verts[nverts*3],bestpt);
rcVcopy(&verts[nverts*3],bestpt);
nverts++;
// Create new triangulation.
// TODO: Incremental add instead of full rebuild.
edges.resize(0);
tris.resize(0);
idx.resize(0);
delaunay(nverts, verts, idx, tris, edges);
delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges);
}
}
if (nverts >= MAX_VERTS)
break;
}
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;
@@ -562,82 +742,178 @@ static bool buildPolyDetail(const float* in, const int nin, unsigned short reg,
static void getHeightData(const rcCompactHeightfield& chf,
const unsigned short* poly, const int npoly,
const unsigned short* verts,
const unsigned short* verts, const int bs,
rcHeightPatch& hp, rcIntArray& stack)
{
// Floodfill the heightfield to get 2D height data,
// starting at vertex locations as seeds.
memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);
// 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)
{
const int ax = (int)verts[poly[j]*3+0];
const int ay = (int)verts[poly[j]*3+1];
const int az = (int)verts[poly[j]*3+2];
if (ax < hp.xmin || ax >= hp.xmin+hp.width ||
az < hp.ymin || az >= hp.ymin+hp.height)
continue;
const rcCompactCell& c = chf.cells[ax+az*chf.width];
int dmin = 0xffff;
int ai = -1;
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
int cx = 0, cz = 0, ci =-1;
int dmin = RC_UNSET_HEIGHT;
for (int k = 0; k < 9; ++k)
{
const rcCompactSpan& s = chf.spans[i];
int d = rcAbs(ay - (int)s.y);
if (d < dmin)
const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0];
const int ay = (int)verts[poly[j]*3+1];
const int az = (int)verts[poly[j]*3+2] + offset[k*2+1];
if (ax < hp.xmin || ax >= hp.xmin+hp.width ||
az < hp.ymin || az >= hp.ymin+hp.height)
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)
{
ai = i;
dmin = d;
const rcCompactSpan& s = chf.spans[i];
int d = rcAbs(ay - (int)s.y);
if (d < dmin)
{
cx = ax;
cz = az;
ci = i;
dmin = d;
}
}
}
if (ai != -1)
if (ci != -1)
{
stack.push(ax);
stack.push(az);
stack.push(ai);
stack.push(cx);
stack.push(cz);
stack.push(ci);
}
}
// Find center of the polygon using flood fill.
int pcx = 0, pcz = 0;
for (int j = 0; j < npoly; ++j)
{
pcx += (int)verts[poly[j]*3+0];
pcz += (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;
}
while (stack.size() > 0)
{
int ci = stack.pop();
int cy = stack.pop();
int cx = stack.pop();
// Skip already visited locations.
int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width;
if (hp.data[idx] != 0xffff)
continue;
// Check if close to center of the polygon.
if (rcAbs(cx-pcx) <= 1 && rcAbs(cy-pcz) <= 1)
{
stack.resize(0);
stack.push(cx);
stack.push(cy);
stack.push(ci);
break;
}
const rcCompactSpan& cs = chf.spans[ci];
hp.data[idx] = cs.y;
for (int dir = 0; dir < 4; ++dir)
{
if (rcGetCon(cs, dir) == 0xf) continue;
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))
continue;
if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0xffff)
if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0)
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);
int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width;
hp.data[idx] = 1;
stack.push(ax);
stack.push(ay);
stack.push(ai);
}
}
}
memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);
// Mark start locations.
for (int i = 0; i < stack.size(); i += 3)
{
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;
}
static const int RETRACT_SIZE = 256;
int head = 0;
while (head*3 < stack.size())
{
int cx = stack[head*3+0];
int cy = stack[head*3+1];
int ci = stack[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);
}
const rcCompactSpan& cs = chf.spans[ci];
for (int dir = 0; dir < 4; ++dir)
{
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))
continue;
if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != RC_UNSET_HEIGHT)
continue;
const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*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);
}
}
}
static unsigned char getEdgeFlags(const float* va, const float* vb,
@@ -664,51 +940,48 @@ static unsigned char getTriFlags(const float* va, const float* vb, const float*
return flags;
}
bool rcBuildPolyMeshDetail(const rcPolyMesh& mesh, const rcCompactHeightfield& chf,
/// @par
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocPolyMeshDetail, rcPolyMesh, rcCompactHeightfield, rcPolyMeshDetail, rcConfig
bool rcBuildPolyMeshDetail(rcContext* ctx, const rcPolyMesh& mesh, const rcCompactHeightfield& chf,
const float sampleDist, const float sampleMaxError,
rcPolyMeshDetail& dmesh)
{
rcAssert(ctx);
ctx->startTimer(RC_TIMER_BUILD_POLYMESHDETAIL);
if (mesh.nverts == 0 || mesh.npolys == 0)
return true;
rcTimeVal startTime = rcGetPerformanceTimer();
rcTimeVal endTime;
int vcap;
int tcap;
const int nvp = mesh.nvp;
const float cs = mesh.cs;
const float ch = mesh.ch;
const float* orig = mesh.bmin;
const int borderSize = mesh.borderSize;
rcIntArray edges(64);
rcIntArray tris(512);
rcIntArray idx(512);
rcIntArray stack(512);
rcIntArray samples(512);
float verts[256*3];
float* poly = 0;
int* bounds = 0;
rcHeightPatch hp;
int nPolyVerts = 0;
int maxhw = 0, maxhh = 0;
bounds = new int[mesh.npolys*4];
rcScopedDelete<int> bounds = (int*)rcAlloc(sizeof(int)*mesh.npolys*4, RC_ALLOC_TEMP);
if (!bounds)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' (%d).", mesh.npolys*4);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'bounds' (%d).", mesh.npolys*4);
return false;
}
poly = new float[nvp*3];
if (!bounds)
rcScopedDelete<float> poly = (float*)rcAlloc(sizeof(float)*nvp*3, RC_ALLOC_TEMP);
if (!poly)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' (%d).", nvp*3);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'poly' (%d).", nvp*3);
return false;
}
// Find max size for a polygon area.
@@ -725,7 +998,7 @@ bool rcBuildPolyMeshDetail(const rcPolyMesh& mesh, const rcCompactHeightfield& c
ymax = 0;
for (int j = 0; j < nvp; ++j)
{
if(p[j] == 0xffff) break;
if(p[j] == RC_MESH_NULL_IDX) break;
const unsigned short* v = &mesh.verts[p[j]*3];
xmin = rcMin(xmin, (int)v[0]);
xmax = rcMax(xmax, (int)v[0]);
@@ -742,58 +1015,54 @@ bool rcBuildPolyMeshDetail(const rcPolyMesh& mesh, const rcCompactHeightfield& c
maxhh = rcMax(maxhh, ymax-ymin);
}
hp.data = new unsigned short[maxhw*maxhh];
hp.data = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxhw*maxhh, RC_ALLOC_TEMP);
if (!hp.data)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' (%d).", maxhw*maxhh);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'hp.data' (%d).", maxhw*maxhh);
return false;
}
dmesh.nmeshes = mesh.npolys;
dmesh.nverts = 0;
dmesh.ntris = 0;
dmesh.meshes = new unsigned short[dmesh.nmeshes*4];
dmesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*dmesh.nmeshes*4, RC_ALLOC_PERM);
if (!dmesh.meshes)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes*4);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.meshes' (%d).", dmesh.nmeshes*4);
return false;
}
vcap = nPolyVerts+nPolyVerts/2;
tcap = vcap*2;
int vcap = nPolyVerts+nPolyVerts/2;
int tcap = vcap*2;
dmesh.nverts = 0;
dmesh.verts = new float[vcap*3];
dmesh.verts = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM);
if (!dmesh.verts)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", vcap*3);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", vcap*3);
return false;
}
dmesh.ntris = 0;
dmesh.tris = new unsigned char[tcap*4];
dmesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char*)*tcap*4, RC_ALLOC_PERM);
if (!dmesh.tris)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", tcap*4);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", tcap*4);
return false;
}
for (int i = 0; i < mesh.npolys; ++i)
{
const unsigned short* p = &mesh.polys[i*nvp*2];
// Find polygon bounding box.
// Store polygon vertices for processing.
int npoly = 0;
for (int j = 0; j < nvp; ++j)
{
if(p[j] == 0xffff) break;
if(p[j] == RC_MESH_NULL_IDX) break;
const unsigned short* v = &mesh.verts[p[j]*3];
poly[j*3+0] = orig[0] + v[0]*cs;
poly[j*3+1] = orig[1] + v[1]*ch;
poly[j*3+2] = orig[2] + v[2]*cs;
poly[j*3+0] = v[0]*cs;
poly[j*3+1] = v[1]*ch;
poly[j*3+2] = v[2]*cs;
npoly++;
}
@@ -802,29 +1071,40 @@ bool rcBuildPolyMeshDetail(const rcPolyMesh& mesh, const rcCompactHeightfield& c
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, hp, stack);
getHeightData(chf, p, npoly, mesh.verts, borderSize, hp, stack);
// Build detail mesh.
int nverts = 0;
if (!buildPolyDetail(poly, npoly, mesh.regs[i],
if (!buildPolyDetail(ctx, poly, npoly,
sampleDist, sampleMaxError,
chf, hp, verts, nverts, tris,
edges, idx, samples))
edges, samples))
{
goto failure;
return false;
}
// Offset detail vertices, unnecassary?
// Move detail verts to world space.
for (int j = 0; j < nverts; ++j)
verts[j*3+1] += chf.ch;
{
verts[j*3+0] += orig[0];
verts[j*3+1] += orig[1] + chf.ch; // Is this offset necessary?
verts[j*3+2] += orig[2];
}
// Offset poly too, will be used to flag checking.
for (int j = 0; j < npoly; ++j)
{
poly[j*3+0] += orig[0];
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] = dmesh.nverts;
dmesh.meshes[i*4+1] = (unsigned short)nverts;
dmesh.meshes[i*4+2] = dmesh.ntris;
dmesh.meshes[i*4+3] = (unsigned short)ntris;
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;
// Store vertices, allocate more memory if necessary.
if (dmesh.nverts+nverts > vcap)
@@ -832,16 +1112,15 @@ bool rcBuildPolyMeshDetail(const rcPolyMesh& mesh, const rcCompactHeightfield& c
while (dmesh.nverts+nverts > vcap)
vcap += 256;
float* newv = new float[vcap*3];
float* newv = (float*)rcAlloc(sizeof(float)*vcap*3, RC_ALLOC_PERM);
if (!newv)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' (%d).", vcap*3);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newv' (%d).", vcap*3);
return false;
}
if (dmesh.nverts)
memcpy(newv, dmesh.verts, sizeof(float)*3*dmesh.nverts);
delete [] dmesh.verts;
rcFree(dmesh.verts);
dmesh.verts = newv;
}
for (int j = 0; j < nverts; ++j)
@@ -857,16 +1136,15 @@ bool rcBuildPolyMeshDetail(const rcPolyMesh& mesh, const rcCompactHeightfield& c
{
while (dmesh.ntris+ntris > tcap)
tcap += 256;
unsigned char* newt = new unsigned char[tcap*4];
unsigned char* newt = (unsigned char*)rcAlloc(sizeof(unsigned char)*tcap*4, RC_ALLOC_PERM);
if (!newt)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' (%d).", tcap*4);
goto failure;
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'newt' (%d).", tcap*4);
return false;
}
if (dmesh.ntris)
memcpy(newt, dmesh.tris, sizeof(unsigned char)*4*dmesh.ntris);
delete [] dmesh.tris;
rcFree(dmesh.tris);
dmesh.tris = newt;
}
for (int j = 0; j < ntris; ++j)
@@ -879,29 +1157,19 @@ bool rcBuildPolyMeshDetail(const rcPolyMesh& mesh, const rcCompactHeightfield& c
dmesh.ntris++;
}
}
delete [] bounds;
delete [] poly;
endTime = rcGetPerformanceTimer();
if (rcGetBuildTimes())
rcGetBuildTimes()->buildDetailMesh += rcGetDeltaTimeUsec(startTime, endTime);
ctx->stopTimer(RC_TIMER_BUILD_POLYMESHDETAIL);
return true;
failure:
delete [] bounds;
delete [] poly;
return false;
}
bool rcMergePolyMeshDetails(rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh)
/// @see rcAllocPolyMeshDetail, rcPolyMeshDetail
bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh)
{
rcTimeVal startTime = rcGetPerformanceTimer();
rcAssert(ctx);
ctx->startTimer(RC_TIMER_MERGE_POLYMESHDETAIL);
int maxVerts = 0;
int maxTris = 0;
int maxMeshes = 0;
@@ -915,29 +1183,26 @@ bool rcMergePolyMeshDetails(rcPolyMeshDetail** meshes, const int nmeshes, rcPoly
}
mesh.nmeshes = 0;
mesh.meshes = new unsigned short[maxMeshes*4];
mesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*maxMeshes*4, RC_ALLOC_PERM);
if (!mesh.meshes)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes*4);
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes*4);
return false;
}
mesh.ntris = 0;
mesh.tris = new unsigned char[maxTris*4];
mesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris*4, RC_ALLOC_PERM);
if (!mesh.tris)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris*4);
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris*4);
return false;
}
mesh.nverts = 0;
mesh.verts = new float[maxVerts*3];
mesh.verts = (float*)rcAlloc(sizeof(float)*maxVerts*3, RC_ALLOC_PERM);
if (!mesh.verts)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", maxVerts*3);
ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", maxVerts*3);
return false;
}
@@ -948,18 +1213,18 @@ bool rcMergePolyMeshDetails(rcPolyMeshDetail** meshes, const int nmeshes, rcPoly
if (!dm) continue;
for (int j = 0; j < dm->nmeshes; ++j)
{
unsigned short* dst = &mesh.meshes[mesh.nmeshes*4];
unsigned short* src = &dm->meshes[j*4];
dst[0] = mesh.nverts+src[0];
unsigned int* dst = &mesh.meshes[mesh.nmeshes*4];
unsigned int* src = &dm->meshes[j*4];
dst[0] = (unsigned int)mesh.nverts+src[0];
dst[1] = src[1];
dst[2] = mesh.ntris+src[2];
dst[2] = (unsigned int)mesh.ntris+src[2];
dst[3] = src[3];
mesh.nmeshes++;
}
for (int k = 0; k < dm->nverts; ++k)
{
vcopy(&mesh.verts[mesh.nverts*3], &dm->verts[k*3]);
rcVcopy(&mesh.verts[mesh.nverts*3], &dm->verts[k*3]);
mesh.nverts++;
}
for (int k = 0; k < dm->ntris; ++k)
@@ -972,10 +1237,7 @@ bool rcMergePolyMeshDetails(rcPolyMeshDetail** meshes, const int nmeshes, rcPoly
}
}
rcTimeVal endTime = rcGetPerformanceTimer();
if (rcGetBuildTimes())
rcGetBuildTimes()->mergePolyMeshDetail += rcGetDeltaTimeUsec(startTime, endTime);
ctx->stopTimer(RC_TIMER_MERGE_POLYMESHDETAIL);
return true;
}

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2009 Mikko Mononen memon@inside.org
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
@@ -20,8 +20,8 @@
#include <math.h>
#include <stdio.h>
#include "Recast.h"
#include "RecastTimer.h"
#include "RecastLog.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"
inline bool overlapBounds(const float* amin, const float* amax, const float* bmin, const float* bmax)
{
@@ -48,8 +48,7 @@ static rcSpan* allocSpan(rcHeightfield& hf)
{
// Create new page.
// Allocate memory for the new pool.
const int size = (sizeof(rcSpanPool)-sizeof(rcSpan)) + sizeof(rcSpan)*RC_SPANS_PER_POOL;
rcSpanPool* pool = reinterpret_cast<rcSpanPool*>(new unsigned char[size]);
rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM);
if (!pool) return 0;
pool->next = 0;
// Add the pool into the list of pools.
@@ -83,16 +82,17 @@ static void freeSpan(rcHeightfield& hf, rcSpan* ptr)
hf.freelist = ptr;
}
static void addSpan(rcHeightfield& hf, int x, int y,
unsigned short smin, unsigned short smax,
unsigned short flags)
static void addSpan(rcHeightfield& hf, const int x, const int y,
const unsigned short smin, const unsigned short smax,
const unsigned char area, const int flagMergeThr)
{
int idx = x + y*hf.width;
rcSpan* s = allocSpan(hf);
s->smin = smin;
s->smax = smax;
s->flags = flags;
s->area = area;
s->next = 0;
// Empty cell, add he first span.
@@ -127,9 +127,8 @@ static void addSpan(rcHeightfield& hf, int x, int y,
s->smax = cur->smax;
// Merge flags.
// if (s->smax == cur->smax)
if (rcAbs((int)s->smax - (int)cur->smax) <= 1)
s->flags |= cur->flags;
if (rcAbs((int)s->smax - (int)cur->smax) <= flagMergeThr)
s->area = rcMax(s->area, cur->area);
// Remove current span.
rcSpan* next = cur->next;
@@ -155,6 +154,21 @@ static void addSpan(rcHeightfield& hf, int x, int y,
}
}
/// @par
///
/// The span addition can be set to favor flags. If the span is merged to
/// another span and the new @p smax is within @p flagMergeThr units
/// from the existing span, the span flags are merged.
///
/// @see rcHeightfield, rcSpan.
void 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);
}
static int clipPoly(const float* in, int n, float* out, float pnx, float pnz, float pd)
{
float d[12];
@@ -186,9 +200,10 @@ static int clipPoly(const float* in, int n, float* out, float pnx, float pnz, fl
}
static void rasterizeTri(const float* v0, const float* v1, const float* v2,
unsigned char flags, rcHeightfield& hf,
const unsigned char area, rcHeightfield& hf,
const float* bmin, const float* bmax,
const float cs, const float ics, const float ich)
const float cs, const float ics, const float ich,
const int flagMergeThr)
{
const int w = hf.width;
const int h = hf.height;
@@ -196,12 +211,12 @@ static void rasterizeTri(const float* v0, const float* v1, const float* v2,
const float by = bmax[1] - bmin[1];
// Calculate the bounding box of the triangle.
vcopy(tmin, v0);
vcopy(tmax, v0);
vmin(tmin, v1);
vmin(tmin, v2);
vmax(tmax, v1);
vmax(tmax, v2);
rcVcopy(tmin, v0);
rcVcopy(tmax, v0);
rcVmin(tmin, v1);
rcVmin(tmin, v2);
rcVmax(tmax, v1);
rcVmax(tmax, v2);
// If the triangle does not touch the bbox of the heightfield, skip the triagle.
if (!overlapBounds(bmin, bmax, tmin, tmax))
@@ -223,9 +238,9 @@ static void rasterizeTri(const float* v0, const float* v1, const float* v2,
for (int y = y0; y <= y1; ++y)
{
// Clip polygon to row.
vcopy(&in[0], v0);
vcopy(&in[1*3], v1);
vcopy(&in[2*3], v2);
rcVcopy(&in[0], v0);
rcVcopy(&in[1*3], v1);
rcVcopy(&in[2*3], v2);
int nvrow = 3;
const float cz = bmin[2] + y*cs;
nvrow = clipPoly(in, nvrow, out, 0, 1, -cz);
@@ -256,38 +271,50 @@ static void rasterizeTri(const float* v0, const float* v1, const float* v2,
if (smax < 0.0f) continue;
if (smin > by) continue;
// Clamp the span to the heightfield bbox.
if (smin < 0.0f) smin = bmin[1];
if (smax > by) smax = bmax[1];
if (smin < 0.0f) smin = 0;
if (smax > by) smax = by;
// Snap the span to the heightfield height grid.
unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, 0x7fff);
unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), 0, 0x7fff);
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, flags);
addSpan(hf, x, y, ismin, ismax, area, flagMergeThr);
}
}
}
void rcRasterizeTriangle(const float* v0, const float* v1, const float* v2,
unsigned char flags, rcHeightfield& solid)
/// @par
///
/// 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,
const unsigned char area, rcHeightfield& solid,
const int flagMergeThr)
{
rcTimeVal startTime = rcGetPerformanceTimer();
rcAssert(ctx);
ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES);
const float ics = 1.0f/solid.cs;
const float ich = 1.0f/solid.ch;
rasterizeTri(v0, v1, v2, flags, solid, solid.bmin, solid.bmax, solid.cs, ics, ich);
rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr);
rcTimeVal endTime = rcGetPerformanceTimer();
if (rcGetBuildTimes())
rcGetBuildTimes()->rasterizeTriangles += rcGetDeltaTimeUsec(startTime, endTime);
ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES);
}
void rcRasterizeTriangles(const float* verts, int nv,
const int* tris, const unsigned char* flags, int nt,
rcHeightfield& solid)
/// @par
///
/// Spans will only be added for triangles that overlap the heightfield grid.
///
/// @see rcHeightfield
void rcRasterizeTriangles(rcContext* ctx, const float* verts, const int /*nv*/,
const int* tris, const unsigned char* areas, const int nt,
rcHeightfield& solid, const int flagMergeThr)
{
rcTimeVal startTime = rcGetPerformanceTimer();
rcAssert(ctx);
ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES);
const float ics = 1.0f/solid.cs;
const float ich = 1.0f/solid.ch;
@@ -298,11 +325,63 @@ void rcRasterizeTriangles(const float* verts, 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, flags[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich);
rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr);
}
rcTimeVal endTime = rcGetPerformanceTimer();
if (rcGetBuildTimes())
rcGetBuildTimes()->rasterizeTriangles += rcGetDeltaTimeUsec(startTime, endTime);
ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES);
}
/// @par
///
/// Spans will only be added for triangles that overlap the heightfield grid.
///
/// @see rcHeightfield
void 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);
const float ics = 1.0f/solid.cs;
const float ich = 1.0f/solid.ch;
// Rasterize triangles.
for (int i = 0; i < nt; ++i)
{
const float* v0 = &verts[tris[i*3+0]*3];
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);
}
ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES);
}
/// @par
///
/// 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,
rcHeightfield& solid, const int flagMergeThr)
{
rcAssert(ctx);
ctx->startTimer(RC_TIMER_RASTERIZE_TRIANGLES);
const float ics = 1.0f/solid.cs;
const float ich = 1.0f/solid.ch;
// Rasterize triangles.
for (int i = 0; i < nt; ++i)
{
const float* v0 = &verts[(i*3+0)*3];
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);
}
ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES);
}

View File

@@ -1,5 +1,5 @@
//
// Copyright (c) 2009 Mikko Mononen memon@inside.org
// Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
@@ -23,13 +23,12 @@
#include <stdlib.h>
#include <stdio.h>
#include "Recast.h"
#include "RecastLog.h"
#include "RecastTimer.h"
#include "RecastAlloc.h"
#include "RecastAssert.h"
#include <new>
static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
unsigned short* src, unsigned short* dst,
unsigned short& maxDist)
static void calculateDistanceField(rcCompactHeightfield& chf, unsigned short* src, unsigned short& maxDist)
{
const int w = chf.width;
const int h = chf.height;
@@ -47,11 +46,19 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
const rcCompactSpan& s = chf.spans[i];
const unsigned char area = chf.areas[i];
int nc = 0;
for (int dir = 0; dir < 4; ++dir)
{
if (rcGetCon(s, dir) != 0xf)
nc++;
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);
if (area == chf.areas[ai])
nc++;
}
}
if (nc != 4)
src[i] = 0;
@@ -59,6 +66,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
}
}
// Pass 1
for (int y = 0; y < h; ++y)
{
@@ -69,7 +77,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
{
const rcCompactSpan& s = chf.spans[i];
if (rcGetCon(s, 0) != 0xf)
if (rcGetCon(s, 0) != RC_NOT_CONNECTED)
{
// (-1,0)
const int ax = x + rcGetDirOffsetX(0);
@@ -80,7 +88,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
src[i] = src[ai]+2;
// (-1,-1)
if (rcGetCon(as, 3) != 0xf)
if (rcGetCon(as, 3) != RC_NOT_CONNECTED)
{
const int aax = ax + rcGetDirOffsetX(3);
const int aay = ay + rcGetDirOffsetY(3);
@@ -89,7 +97,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
src[i] = src[aai]+3;
}
}
if (rcGetCon(s, 3) != 0xf)
if (rcGetCon(s, 3) != RC_NOT_CONNECTED)
{
// (0,-1)
const int ax = x + rcGetDirOffsetX(3);
@@ -100,7 +108,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
src[i] = src[ai]+2;
// (1,-1)
if (rcGetCon(as, 2) != 0xf)
if (rcGetCon(as, 2) != RC_NOT_CONNECTED)
{
const int aax = ax + rcGetDirOffsetX(2);
const int aay = ay + rcGetDirOffsetY(2);
@@ -123,7 +131,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
{
const rcCompactSpan& s = chf.spans[i];
if (rcGetCon(s, 2) != 0xf)
if (rcGetCon(s, 2) != RC_NOT_CONNECTED)
{
// (1,0)
const int ax = x + rcGetDirOffsetX(2);
@@ -134,7 +142,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
src[i] = src[ai]+2;
// (1,1)
if (rcGetCon(as, 1) != 0xf)
if (rcGetCon(as, 1) != RC_NOT_CONNECTED)
{
const int aax = ax + rcGetDirOffsetX(1);
const int aay = ay + rcGetDirOffsetY(1);
@@ -143,7 +151,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
src[i] = src[aai]+3;
}
}
if (rcGetCon(s, 1) != 0xf)
if (rcGetCon(s, 1) != RC_NOT_CONNECTED)
{
// (0,1)
const int ax = x + rcGetDirOffsetX(1);
@@ -154,7 +162,7 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
src[i] = src[ai]+2;
// (-1,1)
if (rcGetCon(as, 0) != 0xf)
if (rcGetCon(as, 0) != RC_NOT_CONNECTED)
{
const int aax = ax + rcGetDirOffsetX(0);
const int aay = ay + rcGetDirOffsetY(0);
@@ -171,8 +179,6 @@ static unsigned short* calculateDistanceField(rcCompactHeightfield& chf,
for (int i = 0; i < chf.spanCount; ++i)
maxDist = rcMax(src[i], maxDist);
return src;
}
static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr,
@@ -191,17 +197,17 @@ static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr,
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
const rcCompactSpan& s = chf.spans[i];
int cd = (int)src[i];
const unsigned short cd = src[i];
if (cd <= thr)
{
dst[i] = cd;
continue;
}
int d = cd;
int d = (int)cd;
for (int dir = 0; dir < 4; ++dir)
{
if (rcGetCon(s, dir) != 0xf)
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
{
const int ax = x + rcGetDirOffsetX(dir);
const int ay = y + rcGetDirOffsetY(dir);
@@ -210,7 +216,7 @@ static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr,
const rcCompactSpan& as = chf.spans[ai];
const int dir2 = (dir+1) & 0x3;
if (rcGetCon(as, dir2) != 0xf)
if (rcGetCon(as, dir2) != RC_NOT_CONNECTED)
{
const int ax2 = ax + rcGetDirOffsetX(dir2);
const int ay2 = ay + rcGetDirOffsetY(dir2);
@@ -236,22 +242,24 @@ static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr,
static bool floodRegion(int x, int y, int i,
unsigned short level, unsigned short minLevel, unsigned short r,
unsigned short level, unsigned short r,
rcCompactHeightfield& chf,
unsigned short* src,
unsigned short* srcReg, unsigned short* srcDist,
rcIntArray& stack)
{
const int w = chf.width;
const unsigned char area = chf.areas[i];
// Flood fill mark region.
stack.resize(0);
stack.push((int)x);
stack.push((int)y);
stack.push((int)i);
src[i*2] = r;
src[i*2+1] = 0;
srcReg[i] = r;
srcDist[i] = 0;
unsigned short lev = level >= minLevel+2 ? level-2 : minLevel;
unsigned short lev = level >= 2 ? level-2 : 0;
int count = 0;
while (stack.size() > 0)
@@ -267,25 +275,30 @@ static bool floodRegion(int x, int y, int i,
for (int dir = 0; dir < 4; ++dir)
{
// 8 connected
if (rcGetCon(cs, dir) != 0xf)
if (rcGetCon(cs, dir) != RC_NOT_CONNECTED)
{
const int ax = cx + rcGetDirOffsetX(dir);
const int ay = cy + rcGetDirOffsetY(dir);
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);
unsigned short nr = src[ai*2];
if (chf.areas[ai] != area)
continue;
unsigned short nr = srcReg[ai];
if (nr & RC_BORDER_REG) // Do not take borders into account.
continue;
if (nr != 0 && nr != r)
ar = nr;
const rcCompactSpan& as = chf.spans[ai];
const int dir2 = (dir+1) & 0x3;
if (rcGetCon(as, dir2) != 0xf)
if (rcGetCon(as, dir2) != RC_NOT_CONNECTED)
{
const int ax2 = ax + rcGetDirOffsetX(dir2);
const int ay2 = ay + rcGetDirOffsetY(dir2);
const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);
unsigned short nr = src[ai2*2];
if (chf.areas[ai2] != area)
continue;
unsigned short nr = srcReg[ai2];
if (nr != 0 && nr != r)
ar = nr;
}
@@ -293,7 +306,7 @@ static bool floodRegion(int x, int y, int i,
}
if (ar != 0)
{
src[ci*2] = 0;
srcReg[ci] = 0;
continue;
}
count++;
@@ -301,21 +314,20 @@ static bool floodRegion(int x, int y, int i,
// Expand neighbours.
for (int dir = 0; dir < 4; ++dir)
{
if (rcGetCon(cs, dir) != 0xf)
if (rcGetCon(cs, dir) != RC_NOT_CONNECTED)
{
const int ax = cx + rcGetDirOffsetX(dir);
const int ay = cy + rcGetDirOffsetY(dir);
const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);
if (chf.spans[ai].dist >= lev)
if (chf.areas[ai] != area)
continue;
if (chf.dist[ai] >= lev && srcReg[ai] == 0)
{
if (src[ai*2] == 0)
{
src[ai*2] = r;
src[ai*2+1] = 0;
stack.push(ax);
stack.push(ay);
stack.push(ai);
}
srcReg[ai] = r;
srcDist[ai] = 0;
stack.push(ax);
stack.push(ay);
stack.push(ai);
}
}
}
@@ -326,8 +338,8 @@ static bool floodRegion(int x, int y, int i,
static unsigned short* expandRegions(int maxIter, unsigned short level,
rcCompactHeightfield& chf,
unsigned short* src,
unsigned short* dst,
unsigned short* srcReg, unsigned short* srcDist,
unsigned short* dstReg, unsigned short* dstDist,
rcIntArray& stack)
{
const int w = chf.width;
@@ -342,7 +354,7 @@ static unsigned short* expandRegions(int maxIter, unsigned short level,
const rcCompactCell& c = chf.cells[x+y*w];
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
if (chf.spans[i].dist >= level && src[i*2] == 0)
if (chf.dist[i] >= level && srcReg[i] == 0 && chf.areas[i] != RC_NULL_AREA)
{
stack.push(x);
stack.push(y);
@@ -357,7 +369,8 @@ static unsigned short* expandRegions(int maxIter, unsigned short level,
{
int failed = 0;
memcpy(dst, src, sizeof(unsigned short)*chf.spanCount*2);
memcpy(dstReg, srcReg, sizeof(unsigned short)*chf.spanCount);
memcpy(dstDist, srcDist, sizeof(unsigned short)*chf.spanCount);
for (int j = 0; j < stack.size(); j += 3)
{
@@ -370,29 +383,31 @@ static unsigned short* expandRegions(int maxIter, unsigned short level,
continue;
}
unsigned short r = src[i*2];
unsigned short r = srcReg[i];
unsigned short d2 = 0xffff;
const unsigned char area = chf.areas[i];
const rcCompactSpan& s = chf.spans[i];
for (int dir = 0; dir < 4; ++dir)
{
if (rcGetCon(s, dir) == 0xf) continue;
if (rcGetCon(s, dir) == RC_NOT_CONNECTED) continue;
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);
if (src[ai*2] > 0 && (src[ai*2] & RC_BORDER_REG) == 0)
if (chf.areas[ai] != area) continue;
if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0)
{
if ((int)src[ai*2+1]+2 < (int)d2)
if ((int)srcDist[ai]+2 < (int)d2)
{
r = src[ai*2];
d2 = src[ai*2+1]+2;
r = srcReg[ai];
d2 = srcDist[ai]+2;
}
}
}
if (r)
{
stack[j+2] = -1; // mark as used
dst[i*2] = r;
dst[i*2+1] = d2;
dstReg[i] = r;
dstDist[i] = d2;
}
else
{
@@ -401,7 +416,8 @@ static unsigned short* expandRegions(int maxIter, unsigned short level,
}
// rcSwap source and dest.
rcSwap(src, dst);
rcSwap(srcReg, dstReg);
rcSwap(srcDist, dstDist);
if (failed*3 == stack.size())
break;
@@ -414,17 +430,25 @@ static unsigned short* expandRegions(int maxIter, unsigned short level,
}
}
return src;
return srcReg;
}
struct rcRegion
{
inline rcRegion() : count(0), id(0), remap(false) {}
inline rcRegion(unsigned short i) :
spanCount(0),
id(i),
areaType(0),
remap(false),
visited(false)
{}
int count;
unsigned short id;
int spanCount; // Number of spans belonging to this region
unsigned short id; // ID of the region
unsigned char areaType; // Are type.
bool remap;
bool visited;
rcIntArray connections;
rcIntArray floors;
};
@@ -467,25 +491,27 @@ static void replaceNeighbour(rcRegion& reg, unsigned short oldId, unsigned short
removeAdjacentNeighbours(reg);
}
static bool canMergeWithRegion(rcRegion& reg, unsigned short id)
static bool canMergeWithRegion(const rcRegion& rega, const rcRegion& regb)
{
if (rega.areaType != regb.areaType)
return false;
int n = 0;
for (int i = 0; i < reg.connections.size(); ++i)
for (int i = 0; i < rega.connections.size(); ++i)
{
if (reg.connections[i] == id)
if (rega.connections[i] == regb.id)
n++;
}
if (n > 1)
return false;
for (int i = 0; i < reg.floors.size(); ++i)
for (int i = 0; i < rega.floors.size(); ++i)
{
if (reg.floors[i] == id)
if (rega.floors[i] == regb.id)
return false;
}
return true;
}
static void addUniqueFloorRegion(rcRegion& reg, unsigned short n)
static void addUniqueFloorRegion(rcRegion& reg, int n)
{
for (int i = 0; i < reg.floors.size(); ++i)
if (reg.floors[i] == n)
@@ -543,8 +569,8 @@ static bool mergeRegions(rcRegion& rega, rcRegion& regb)
for (int j = 0; j < regb.floors.size(); ++j)
addUniqueFloorRegion(rega, regb.floors[j]);
rega.count += regb.count;
regb.count = 0;
rega.spanCount += regb.spanCount;
regb.spanCount = 0;
regb.connections.resize(0);
return true;
@@ -562,26 +588,26 @@ static bool isRegionConnectedToBorder(const rcRegion& reg)
return false;
}
static bool isSolidEdge(rcCompactHeightfield& chf, unsigned short* src,
static bool isSolidEdge(rcCompactHeightfield& chf, unsigned short* srcReg,
int x, int y, int i, int dir)
{
const rcCompactSpan& s = chf.spans[i];
unsigned short r = 0;
if (rcGetCon(s, dir) != 0xf)
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);
r = src[ai*2];
r = srcReg[ai];
}
if (r == src[i*2])
if (r == srcReg[i])
return false;
return true;
}
static void walkContour(int x, int y, int i, int dir,
rcCompactHeightfield& chf,
unsigned short* src,
unsigned short* srcReg,
rcIntArray& cont)
{
int startDir = dir;
@@ -589,12 +615,12 @@ static void walkContour(int x, int y, int i, int dir,
const rcCompactSpan& ss = chf.spans[i];
unsigned short curReg = 0;
if (rcGetCon(ss, dir) != 0xf)
if (rcGetCon(ss, 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(ss, dir);
curReg = src[ai*2];
curReg = srcReg[ai];
}
cont.push(curReg);
@@ -603,16 +629,16 @@ static void walkContour(int x, int y, int i, int dir,
{
const rcCompactSpan& s = chf.spans[i];
if (isSolidEdge(chf, src, x, y, i, dir))
if (isSolidEdge(chf, srcReg, x, y, i, dir))
{
// Choose the edge corner
unsigned short r = 0;
if (rcGetCon(s, dir) != 0xf)
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);
r = src[ai*2];
r = srcReg[ai];
}
if (r != curReg)
{
@@ -627,7 +653,7 @@ static void walkContour(int x, int y, int i, int dir,
int ni = -1;
const int nx = x + rcGetDirOffsetX(dir);
const int ny = y + rcGetDirOffsetY(dir);
if (rcGetCon(s, dir) != 0xf)
if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
{
const rcCompactCell& nc = chf.cells[nx+ny*chf.width];
ni = (int)nc.index + rcGetCon(s, dir);
@@ -667,26 +693,26 @@ static void walkContour(int x, int y, int i, int dir,
}
}
static bool filterSmallRegions(int minRegionSize, int mergeRegionSize,
static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegionSize,
unsigned short& maxRegionId,
rcCompactHeightfield& chf,
unsigned short* src)
unsigned short* srcReg)
{
const int w = chf.width;
const int h = chf.height;
int nreg = maxRegionId+1;
rcRegion* regions = new rcRegion[nreg];
const int nreg = maxRegionId+1;
rcRegion* regions = (rcRegion*)rcAlloc(sizeof(rcRegion)*nreg, RC_ALLOC_TEMP);
if (!regions)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (%d).", nreg);
ctx->log(RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (%d).", nreg);
return false;
}
for (int i = 0; i < nreg; ++i)
regions[i].id = (unsigned short)i;
// Construct regions
for (int i = 0; i < nreg; ++i)
new(&regions[i]) rcRegion((unsigned short)i);
// Find edge of a region and find connections around the contour.
for (int y = 0; y < h; ++y)
{
@@ -695,19 +721,19 @@ static bool filterSmallRegions(int minRegionSize, int mergeRegionSize,
const rcCompactCell& c = chf.cells[x+y*w];
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
unsigned short r = src[i*2];
unsigned short r = srcReg[i];
if (r == 0 || r >= nreg)
continue;
rcRegion& reg = regions[r];
reg.count++;
reg.spanCount++;
// Update floors.
for (int j = (int)c.index; j < ni; ++j)
{
if (i == j) continue;
unsigned short floorId = src[j*2];
unsigned short floorId = srcReg[j];
if (floorId == 0 || floorId >= nreg)
continue;
addUniqueFloorRegion(reg, floorId);
@@ -717,11 +743,13 @@ static bool filterSmallRegions(int minRegionSize, int mergeRegionSize,
if (reg.connections.size() > 0)
continue;
reg.areaType = chf.areas[i];
// Check if this cell is next to a border.
int ndir = -1;
for (int dir = 0; dir < 4; ++dir)
{
if (isSolidEdge(chf, src, x, y, i, dir))
if (isSolidEdge(chf, srcReg, x, y, i, dir))
{
ndir = dir;
break;
@@ -732,33 +760,78 @@ static bool filterSmallRegions(int minRegionSize, int mergeRegionSize,
{
// The cell is at border.
// Walk around the contour to find all the neighbours.
walkContour(x, y, i, ndir, chf, src, reg.connections);
walkContour(x, y, i, ndir, chf, srcReg, reg.connections);
}
}
}
}
// Remove too small unconnected regions.
// Remove too small regions.
rcIntArray stack(32);
rcIntArray trace(32);
for (int i = 0; i < nreg; ++i)
{
rcRegion& reg = regions[i];
if (reg.id == 0 || (reg.id & RC_BORDER_REG))
continue;
if (reg.count == 0)
continue;
if (reg.spanCount == 0)
continue;
if (reg.visited)
continue;
if (reg.connections.size() == 1 && reg.connections[0] == 0)
// Count the total size of all the connected regions.
// Also keep track of the regions connects to a tile border.
bool connectsToBorder = false;
int spanCount = 0;
stack.resize(0);
trace.resize(0);
reg.visited = true;
stack.push(i);
while (stack.size())
{
if (reg.count < minRegionSize)
// Pop
int ri = stack.pop();
rcRegion& creg = regions[ri];
spanCount += creg.spanCount;
trace.push(ri);
for (int j = 0; j < creg.connections.size(); ++j)
{
// Non-connected small region, remove.
reg.count = 0;
reg.id = 0;
if (creg.connections[j] & RC_BORDER_REG)
{
connectsToBorder = true;
continue;
}
rcRegion& nreg = regions[creg.connections[j]];
if (nreg.visited)
continue;
if (nreg.id == 0 || (nreg.id & RC_BORDER_REG))
continue;
// Visit
stack.push(nreg.id);
nreg.visited = true;
}
}
// If the accumulated regions size is too small, remove it.
// Do not remove areas which connect to tile borders
// as their size cannot be estimated correctly and removing them
// can potentially remove necessary areas.
if (spanCount < minRegionArea && !connectsToBorder)
{
// Kill all visited regions.
for (int j = 0; j < trace.size(); ++j)
{
regions[trace[j]].spanCount = 0;
regions[trace[j]].id = 0;
}
}
}
// Merge too small regions to neighbour regions.
int mergeCount = 0 ;
do
@@ -768,14 +841,14 @@ static bool filterSmallRegions(int minRegionSize, int mergeRegionSize,
{
rcRegion& reg = regions[i];
if (reg.id == 0 || (reg.id & RC_BORDER_REG))
continue;
if (reg.count == 0)
continue;
if (reg.spanCount == 0)
continue;
// Check to see if the region should be merged.
if (reg.count > mergeRegionSize && isRegionConnectedToBorder(reg))
if (reg.spanCount > mergeRegionSize && isRegionConnectedToBorder(reg))
continue;
// Small region with more than 1 connection.
// Or region which is not connected to a border at all.
// Find smallest neighbour region that connects to this one.
@@ -786,11 +859,11 @@ static bool filterSmallRegions(int minRegionSize, int mergeRegionSize,
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.count < smallest &&
canMergeWithRegion(reg, mreg.id) &&
canMergeWithRegion(mreg, reg.id))
if (mreg.spanCount < smallest &&
canMergeWithRegion(reg, mreg) &&
canMergeWithRegion(mreg, reg))
{
smallest = mreg.count;
smallest = mreg.spanCount;
mergeId = mreg.id;
}
}
@@ -821,16 +894,16 @@ static bool filterSmallRegions(int minRegionSize, int mergeRegionSize,
}
}
while (mergeCount > 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.
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)
{
@@ -848,91 +921,88 @@ static bool filterSmallRegions(int minRegionSize, int mergeRegionSize,
}
}
maxRegionId = regIdGen;
// Remap regions.
for (int i = 0; i < chf.spanCount; ++i)
{
if ((src[i*2] & RC_BORDER_REG) == 0)
src[i*2] = regions[src[i*2]].id;
if ((srcReg[i] & RC_BORDER_REG) == 0)
srcReg[i] = regions[srcReg[i]].id;
}
delete [] regions;
for (int i = 0; i < nreg; ++i)
regions[i].~rcRegion();
rcFree(regions);
return true;
}
bool rcBuildDistanceField(rcCompactHeightfield& chf)
/// @par
///
/// This is usually the second to the last step in creating a fully built
/// compact heightfield. This step is required before regions are built
/// using #rcBuildRegions or #rcBuildRegionsMonotone.
///
/// After this step, the distance data is available via the rcCompactHeightfield::maxDistance
/// and rcCompactHeightfield::dist fields.
///
/// @see rcCompactHeightfield, rcBuildRegions, rcBuildRegionsMonotone
bool rcBuildDistanceField(rcContext* ctx, rcCompactHeightfield& chf)
{
rcTimeVal startTime = rcGetPerformanceTimer();
rcAssert(ctx);
unsigned short* dist0 = new unsigned short[chf.spanCount];
if (!dist0)
ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD);
if (chf.dist)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dist0' (%d).", chf.spanCount);
rcFree(chf.dist);
chf.dist = 0;
}
unsigned short* src = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP);
if (!src)
{
ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'src' (%d).", chf.spanCount);
return false;
}
unsigned short* dist1 = new unsigned short[chf.spanCount];
if (!dist1)
unsigned short* dst = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP);
if (!dst)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dist1' (%d).", chf.spanCount);
delete [] dist0;
ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dst' (%d).", chf.spanCount);
rcFree(src);
return false;
}
unsigned short* src = dist0;
unsigned short* dst = dist1;
unsigned short maxDist = 0;
rcTimeVal distStartTime = rcGetPerformanceTimer();
if (calculateDistanceField(chf, src, dst, maxDist) != src)
rcSwap(src, dst);
ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST);
calculateDistanceField(chf, src, maxDist);
chf.maxDistance = maxDist;
rcTimeVal distEndTime = rcGetPerformanceTimer();
ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST);
rcTimeVal blurStartTime = rcGetPerformanceTimer();
ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR);
// Blur
if (boxBlur(chf, 1, src, dst) != src)
rcSwap(src, dst);
// Store distance.
for (int i = 0; i < chf.spanCount; ++i)
chf.spans[i].dist = src[i];
chf.dist = src;
rcTimeVal blurEndTime = rcGetPerformanceTimer();
ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR);
ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD);
delete [] dist0;
delete [] dist1;
rcTimeVal endTime = rcGetPerformanceTimer();
/* if (rcGetLog())
{
rcGetLog()->log(RC_LOG_PROGRESS, "Build distance field: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
rcGetLog()->log(RC_LOG_PROGRESS, " - dist: %.3f ms", rcGetDeltaTimeUsec(distStartTime, distEndTime)/1000.0f);
rcGetLog()->log(RC_LOG_PROGRESS, " - blur: %.3f ms", rcGetDeltaTimeUsec(blurStartTime, blurEndTime)/1000.0f);
}*/
if (rcGetBuildTimes())
{
rcGetBuildTimes()->buildDistanceField += rcGetDeltaTimeUsec(startTime, endTime);
rcGetBuildTimes()->buildDistanceFieldDist += rcGetDeltaTimeUsec(distStartTime, distEndTime);
rcGetBuildTimes()->buildDistanceFieldBlur += rcGetDeltaTimeUsec(blurStartTime, blurEndTime);
}
rcFree(dst);
return true;
}
static void paintRectRegion(int minx, int maxx, int miny, int maxy,
unsigned short regId, unsigned short minLevel,
rcCompactHeightfield& chf, unsigned short* src)
static void paintRectRegion(int minx, int maxx, int miny, int maxy, unsigned short regId,
rcCompactHeightfield& chf, unsigned short* srcReg)
{
const int w = chf.width;
const int w = chf.width;
for (int y = miny; y < maxy; ++y)
{
for (int x = minx; x < maxx; ++x)
@@ -940,77 +1010,283 @@ static void paintRectRegion(int minx, int maxx, int miny, int maxy,
const rcCompactCell& c = chf.cells[x+y*w];
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
if (chf.spans[i].dist >= minLevel)
src[i*2] = regId;
if (chf.areas[i] != RC_NULL_AREA)
srcReg[i] = regId;
}
}
}
}
bool rcBuildRegions(rcCompactHeightfield& chf,
int walkableRadius, int borderSize,
int minRegionSize, int mergeRegionSize)
static const unsigned short RC_NULL_NEI = 0xffff;
struct rcSweepSpan
{
rcTimeVal startTime = rcGetPerformanceTimer();
unsigned short rid; // row id
unsigned short id; // region id
unsigned short ns; // number samples
unsigned short nei; // neighbour id
};
/// @par
///
/// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour.
/// Contours will form simple polygons.
///
/// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be
/// re-assigned to the zero (null) region.
///
/// Partitioning can result in smaller than necessary regions. @p mergeRegionArea helps
/// reduce unecessarily small regions.
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// The region data will be available via the rcCompactHeightfield::maxRegions
/// and rcCompactSpan::reg fields.
///
/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions.
///
/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig
bool rcBuildRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf,
const int borderSize, const int minRegionArea, const int mergeRegionArea)
{
rcAssert(ctx);
ctx->startTimer(RC_TIMER_BUILD_REGIONS);
const int w = chf.width;
const int h = chf.height;
unsigned short id = 1;
rcScopedDelete<unsigned short> 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<rcSweepSpan> 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;
}
}
}
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;
ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER);
// 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;
}
/// @par
///
/// Non-null regions will consist of connected, non-overlapping walkable spans that form a single contour.
/// Contours will form simple polygons.
///
/// If multiple regions form an area that is smaller than @p minRegionArea, then all spans will be
/// re-assigned to the zero (null) region.
///
/// Watershed partitioning can result in smaller than necessary regions, especially in diagonal corridors.
/// @p mergeRegionArea helps reduce unecessarily small regions.
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// The region data will be available via the rcCompactHeightfield::maxRegions
/// and rcCompactSpan::reg fields.
///
/// @warning The distance field must be created using #rcBuildDistanceField before attempting to build regions.
///
/// @see rcCompactHeightfield, rcCompactSpan, rcBuildDistanceField, rcBuildRegionsMonotone, rcConfig
bool rcBuildRegions(rcContext* ctx, rcCompactHeightfield& chf,
const int borderSize, const int minRegionArea, const int mergeRegionArea)
{
rcAssert(ctx);
ctx->startTimer(RC_TIMER_BUILD_REGIONS);
const int w = chf.width;
const int h = chf.height;
unsigned short* tmp1 = new unsigned short[chf.spanCount*2];
if (!tmp1)
rcScopedDelete<unsigned short> buf = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP);
if (!buf)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'tmp1' (%d).", chf.spanCount*2);
return false;
}
unsigned short* tmp2 = new unsigned short[chf.spanCount*2];
if (!tmp2)
{
if (rcGetLog())
rcGetLog()->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'tmp2' (%d).", chf.spanCount*2);
delete [] tmp1;
ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4);
return false;
}
rcTimeVal regStartTime = rcGetPerformanceTimer();
ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED);
rcIntArray stack(1024);
rcIntArray visited(1024);
unsigned short* src = tmp1;
unsigned short* dst = tmp2;
unsigned short* srcReg = buf;
unsigned short* srcDist = buf+chf.spanCount;
unsigned short* dstReg = buf+chf.spanCount*2;
unsigned short* dstDist = buf+chf.spanCount*3;
memset(src, 0, sizeof(unsigned short) * chf.spanCount*2);
memset(srcReg, 0, sizeof(unsigned short)*chf.spanCount);
memset(srcDist, 0, sizeof(unsigned short)*chf.spanCount);
unsigned short regionId = 1;
unsigned short level = (chf.maxDistance+1) & ~1;
unsigned short minLevel = (unsigned short)(walkableRadius*2);
const int expandIters = 4 + walkableRadius * 2;
// Mark border regions.
paintRectRegion(0, borderSize, 0, h, regionId|RC_BORDER_REG, minLevel, chf, src); regionId++;
paintRectRegion(w-borderSize, w, 0, h, regionId|RC_BORDER_REG, minLevel, chf, src); regionId++;
paintRectRegion(0, w, 0, borderSize, regionId|RC_BORDER_REG, minLevel, chf, src); regionId++;
paintRectRegion(0, w, h-borderSize, h, regionId|RC_BORDER_REG, minLevel, chf, src); regionId++;
// TODO: Figure better formula, expandIters defines how much the
// watershed "overflows" and simplifies the regions. Tying it to
// agent radius was usually good indication how greedy it could be.
// const int expandIters = 4 + walkableRadius * 2;
const int expandIters = 8;
rcTimeVal expTime = 0;
rcTimeVal floodTime = 0;
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, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
paintRectRegion(w-bw, w, 0, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
paintRectRegion(0, w, 0, bh, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
paintRectRegion(0, w, h-bh, h, regionId|RC_BORDER_REG, chf, srcReg); regionId++;
chf.borderSize = borderSize;
}
while (level > minLevel)
while (level > 0)
{
level = level >= 2 ? level-2 : 0;
rcTimeVal expStartTime = rcGetPerformanceTimer();
ctx->startTimer(RC_TIMER_BUILD_REGIONS_EXPAND);
// Expand current regions until no empty connected cells found.
if (expandRegions(expandIters, level, chf, src, dst, stack) != src)
rcSwap(src, dst);
if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg)
{
rcSwap(srcReg, dstReg);
rcSwap(srcDist, dstDist);
}
expTime += rcGetPerformanceTimer() - expStartTime;
ctx->stopTimer(RC_TIMER_BUILD_REGIONS_EXPAND);
rcTimeVal floodStartTime = rcGetPerformanceTimer();
ctx->startTimer(RC_TIMER_BUILD_REGIONS_FLOOD);
// Mark new regions with IDs.
for (int y = 0; y < h; ++y)
@@ -1020,61 +1296,41 @@ bool rcBuildRegions(rcCompactHeightfield& chf,
const rcCompactCell& c = chf.cells[x+y*w];
for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
{
if (chf.spans[i].dist < level || src[i*2] != 0)
if (chf.dist[i] < level || srcReg[i] != 0 || chf.areas[i] == RC_NULL_AREA)
continue;
if (floodRegion(x, y, i, minLevel, level, regionId, chf, src, stack))
if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack))
regionId++;
}
}
}
floodTime += rcGetPerformanceTimer() - floodStartTime;
ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FLOOD);
}
// Expand current regions until no empty connected cells found.
if (expandRegions(expandIters*8, minLevel, chf, src, dst, stack) != src)
rcSwap(src, dst);
if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg)
{
rcSwap(srcReg, dstReg);
rcSwap(srcDist, dstDist);
}
rcTimeVal regEndTime = rcGetPerformanceTimer();
ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED);
rcTimeVal filterStartTime = rcGetPerformanceTimer();
ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER);
// Filter out small regions.
chf.maxRegions = regionId;
if (!filterSmallRegions(minRegionSize, mergeRegionSize, chf.maxRegions, chf, src))
if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg))
return false;
rcTimeVal filterEndTime = rcGetPerformanceTimer();
ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER);
// Write the result out.
for (int i = 0; i < chf.spanCount; ++i)
chf.spans[i].reg = src[i*2];
chf.spans[i].reg = srcReg[i];
delete [] tmp1;
delete [] tmp2;
ctx->stopTimer(RC_TIMER_BUILD_REGIONS);
rcTimeVal endTime = rcGetPerformanceTimer();
/* if (rcGetLog())
{
rcGetLog()->log(RC_LOG_PROGRESS, "Build regions: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
rcGetLog()->log(RC_LOG_PROGRESS, " - reg: %.3f ms", rcGetDeltaTimeUsec(regStartTime, regEndTime)/1000.0f);
rcGetLog()->log(RC_LOG_PROGRESS, " - exp: %.3f ms", rcGetDeltaTimeUsec(0, expTime)/1000.0f);
rcGetLog()->log(RC_LOG_PROGRESS, " - flood: %.3f ms", rcGetDeltaTimeUsec(0, floodTime)/1000.0f);
rcGetLog()->log(RC_LOG_PROGRESS, " - filter: %.3f ms", rcGetDeltaTimeUsec(filterStartTime, filterEndTime)/1000.0f);
}
*/
if (rcGetBuildTimes())
{
rcGetBuildTimes()->buildRegions += rcGetDeltaTimeUsec(startTime, endTime);
rcGetBuildTimes()->buildRegionsReg += rcGetDeltaTimeUsec(regStartTime, regEndTime);
rcGetBuildTimes()->buildRegionsExp += rcGetDeltaTimeUsec(0, expTime);
rcGetBuildTimes()->buildRegionsFlood += rcGetDeltaTimeUsec(0, floodTime);
rcGetBuildTimes()->buildRegionsFilter += rcGetDeltaTimeUsec(filterStartTime, filterEndTime);
}
return true;
}

View File

@@ -30,6 +30,11 @@
#include <math.h>
#include "Recast.h"
static rcContext *sctx;
#define INIT_SCTX() \
if (sctx == NULL) sctx = new rcContext(false)
int recast_buildMeshAdjacency(unsigned short* polys, const int npolys,
const int nverts, const int vertsPerPoly)
{
@@ -48,109 +53,123 @@ void recast_calcGridSize(const float *bmin, const float *bmax, float cs, int *w,
struct recast_heightfield *recast_newHeightfield(void)
{
return (struct recast_heightfield *) (new rcHeightfield);
return (struct recast_heightfield *) rcAllocHeightfield();
}
void recast_destroyHeightfield(struct recast_heightfield *heightfield)
{
delete (rcHeightfield *) heightfield;
rcFreeHeightField((rcHeightfield *) heightfield);
}
int recast_createHeightfield(struct recast_heightfield *hf, int width, int height,
const float *bmin, const float* bmax, float cs, float ch)
{
return rcCreateHeightfield(*(rcHeightfield *)hf, width, height, bmin, bmax, cs, ch);
INIT_SCTX();
return rcCreateHeightfield(sctx, *(rcHeightfield *)hf, width, height, bmin, bmax, cs, ch);
}
void recast_markWalkableTriangles(const float walkableSlopeAngle,const float *verts, int nv,
const int *tris, int nt, unsigned char *flags)
{
rcMarkWalkableTriangles(walkableSlopeAngle, verts, nv, tris, nt, flags);
INIT_SCTX();
rcMarkWalkableTriangles(sctx, walkableSlopeAngle, verts, nv, tris, nt, flags);
}
void recast_rasterizeTriangles(const float *verts, int nv, const int *tris,
const unsigned char *flags, int nt, struct recast_heightfield *solid)
{
rcRasterizeTriangles(verts, nv, tris, flags, nt, *(rcHeightfield *) solid);
INIT_SCTX();
rcRasterizeTriangles(sctx, verts, nv, tris, flags, nt, *(rcHeightfield *) solid);
}
void recast_filterLedgeSpans(const int walkableHeight, const int walkableClimb,
struct recast_heightfield *solid)
{
rcFilterLedgeSpans(walkableHeight, walkableClimb, *(rcHeightfield *) solid);
INIT_SCTX();
rcFilterLedgeSpans(sctx, walkableHeight, walkableClimb, *(rcHeightfield *) solid);
}
void recast_filterWalkableLowHeightSpans(int walkableHeight, struct recast_heightfield *solid)
{
rcFilterWalkableLowHeightSpans(walkableHeight, *(rcHeightfield *) solid);
INIT_SCTX();
rcFilterWalkableLowHeightSpans(sctx, walkableHeight, *(rcHeightfield *) solid);
}
void recast_filterLowHangingWalkableObstacles(const int walkableClimb, struct recast_heightfield *solid)
{
INIT_SCTX();
rcFilterLowHangingWalkableObstacles(sctx, walkableClimb, *(rcHeightfield *) solid);
}
struct recast_compactHeightfield *recast_newCompactHeightfield(void)
{
return (struct recast_compactHeightfield *) (new rcCompactHeightfield);
return (struct recast_compactHeightfield *) rcAllocCompactHeightfield();
}
void recast_destroyCompactHeightfield(struct recast_compactHeightfield *compactHeightfield)
{
delete (rcCompactHeightfield *) compactHeightfield;
rcFreeCompactHeightfield( (rcCompactHeightfield *) compactHeightfield);
}
int recast_buildCompactHeightfield(const int walkableHeight, const int walkableClimb,
unsigned char flags, struct recast_heightfield *hf, struct recast_compactHeightfield *chf)
struct recast_heightfield *hf, struct recast_compactHeightfield *chf)
{
int rcFlags = 0;
INIT_SCTX();
return rcBuildCompactHeightfield(sctx, walkableHeight, walkableClimb,
*(rcHeightfield *) hf, *(rcCompactHeightfield *) chf);
}
if(flags & RECAST_WALKABLE)
rcFlags |= RC_WALKABLE;
if(flags & RECAST_REACHABLE)
rcFlags |= RC_REACHABLE;
return rcBuildCompactHeightfield(walkableHeight, walkableClimb, rcFlags,
*(rcHeightfield *) hf, *(rcCompactHeightfield *) chf);
int recast_erodeWalkableArea(int radius, struct recast_compactHeightfield *chf)
{
INIT_SCTX();
return rcErodeWalkableArea(sctx, radius, *(rcCompactHeightfield *) chf);
}
int recast_buildDistanceField(struct recast_compactHeightfield *chf)
{
return rcBuildDistanceField(*(rcCompactHeightfield *) chf);
INIT_SCTX();
return rcBuildDistanceField(sctx, *(rcCompactHeightfield *) chf);
}
int recast_buildRegions(struct recast_compactHeightfield *chf, int walkableRadius, int borderSize,
int recast_buildRegions(struct recast_compactHeightfield *chf, int borderSize,
int minRegionSize, int mergeRegionSize)
{
return rcBuildRegions(*(rcCompactHeightfield *) chf, walkableRadius, borderSize,
INIT_SCTX();
return rcBuildRegions(sctx, *(rcCompactHeightfield *) chf, borderSize,
minRegionSize, mergeRegionSize);
}
struct recast_contourSet *recast_newContourSet(void)
{
return (struct recast_contourSet *) (new rcContourSet);
return (struct recast_contourSet *) rcAllocContourSet();
}
void recast_destroyContourSet(struct recast_contourSet *contourSet)
{
delete (rcContourSet *) contourSet;
rcFreeContourSet((rcContourSet *) contourSet);
}
int recast_buildContours(struct recast_compactHeightfield *chf,
const float maxError, const int maxEdgeLen, struct recast_contourSet *cset)
{
return rcBuildContours(*(rcCompactHeightfield *) chf, maxError, maxEdgeLen, *(rcContourSet *) cset);
INIT_SCTX();
return rcBuildContours(sctx, *(rcCompactHeightfield *) chf, maxError, maxEdgeLen, *(rcContourSet *) cset);
}
struct recast_polyMesh *recast_newPolyMesh(void)
{
return (recast_polyMesh *) (new rcPolyMesh);
return (recast_polyMesh *) rcAllocPolyMesh();
}
void recast_destroyPolyMesh(struct recast_polyMesh *polyMesh)
{
delete (rcPolyMesh *) polyMesh;
rcFreePolyMesh((rcPolyMesh *) polyMesh);
}
int recast_buildPolyMesh(struct recast_contourSet *cset, int nvp, struct recast_polyMesh *mesh)
{
return rcBuildPolyMesh(*(rcContourSet *) cset, nvp, * (rcPolyMesh *) mesh);
INIT_SCTX();
return rcBuildPolyMesh(sctx, *(rcContourSet *) cset, nvp, * (rcPolyMesh *) mesh);
}
unsigned short *recast_polyMeshGetVerts(struct recast_polyMesh *mesh, int *nverts)
@@ -206,18 +225,19 @@ unsigned short *recast_polyMeshGetPolys(struct recast_polyMesh *mesh, int *npoly
struct recast_polyMeshDetail *recast_newPolyMeshDetail(void)
{
return (struct recast_polyMeshDetail *) (new rcPolyMeshDetail);
return (struct recast_polyMeshDetail *) rcAllocPolyMeshDetail();
}
void recast_destroyPolyMeshDetail(struct recast_polyMeshDetail *polyMeshDetail)
{
delete (rcPolyMeshDetail *) polyMeshDetail;
rcFreePolyMeshDetail((rcPolyMeshDetail *) polyMeshDetail);
}
int recast_buildPolyMeshDetail(const struct recast_polyMesh *mesh, const struct recast_compactHeightfield *chf,
const float sampleDist, const float sampleMaxError, struct recast_polyMeshDetail *dmesh)
{
return rcBuildPolyMeshDetail(*(rcPolyMesh *) mesh, *(rcCompactHeightfield *) chf,
INIT_SCTX();
return rcBuildPolyMeshDetail(sctx, *(rcPolyMesh *) mesh, *(rcCompactHeightfield *) chf,
sampleDist, sampleMaxError, *(rcPolyMeshDetail *) dmesh);
}
@@ -241,7 +261,7 @@ unsigned char *recast_polyMeshDetailGetTris(struct recast_polyMeshDetail *mesh,
return dmesh->tris;
}
unsigned short *recast_polyMeshDetailGetMeshes(struct recast_polyMeshDetail *mesh, int *nmeshes)
unsigned int *recast_polyMeshDetailGetMeshes(struct recast_polyMeshDetail *mesh, int *nmeshes)
{
rcPolyMeshDetail *dmesh = (rcPolyMeshDetail *)mesh;
@@ -250,3 +270,130 @@ unsigned short *recast_polyMeshDetailGetMeshes(struct recast_polyMeshDetail *mes
return dmesh->meshes;
}
// qsort based on FreeBSD source (libkern\qsort.c)
typedef int cmp_t(void *, const void *, const void *);
static inline char *med3(char *, char *, char *, cmp_t *, void *);
static inline void swapfunc(char *, char *, int, int);
#define min(a, b) (a) < (b) ? a : b
#define swapcode(TYPE, parmi, parmj, n) \
{ \
long i = (n) / sizeof (TYPE); \
TYPE *pi = (TYPE *) (parmi); \
TYPE *pj = (TYPE *) (parmj); \
do { \
TYPE t = *pi; \
*pi++ = *pj; \
*pj++ = t; \
} while (--i > 0); \
}
#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
static inline void swapfunc(char* a, char* b, int n, int swaptype)
{
if(swaptype <= 1)
swapcode(long, a, b, n)
else
swapcode(char, a, b, n)
}
#define swap(a, b) \
if (swaptype == 0) { \
long t = *(long *)(a); \
*(long *)(a) = *(long *)(b);\
*(long *)(b) = t; \
} else \
swapfunc(a, b, es, swaptype)
#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype)
#define CMP(t, x, y) (cmp((t), (x), (y)))
static inline char * med3(char *a, char *b, char *c, cmp_t *cmp, void *thunk)
{
return CMP(thunk, a, b) < 0 ?
(CMP(thunk, b, c) < 0 ? b : (CMP(thunk, a, c) < 0 ? c : a ))
:(CMP(thunk, b, c) > 0 ? b : (CMP(thunk, a, c) < 0 ? a : c ));
}
void recast_qsort(void *a, size_t n, size_t es, void *thunk, cmp_t *cmp)
{
char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
int d, r, swaptype, swap_cnt;
loop:
SWAPINIT(a, es);
swap_cnt = 0;
if (n < 7) {
for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es)
for (pl = pm;
pl > (char *)a && CMP(thunk, pl - es, pl) > 0;
pl -= es)
swap(pl, pl - es);
return;
}
pm = (char *)a + (n / 2) * es;
if (n > 7) {
pl = (char *)a;
pn = (char *)a + (n - 1) * es;
if (n > 40) {
d = (n / 8) * es;
pl = med3(pl, pl + d, pl + 2 * d, cmp, thunk);
pm = med3(pm - d, pm, pm + d, cmp, thunk);
pn = med3(pn - 2 * d, pn - d, pn, cmp, thunk);
}
pm = med3(pl, pm, pn, cmp, thunk);
}
swap((char *)a, pm);
pa = pb = (char *)a + es;
pc = pd = (char *)a + (n - 1) * es;
for (;;) {
while (pb <= pc && (r = CMP(thunk, pb, a)) <= 0) {
if (r == 0) {
swap_cnt = 1;
swap(pa, pb);
pa += es;
}
pb += es;
}
while (pb <= pc && (r = CMP(thunk, pc, a)) >= 0) {
if (r == 0) {
swap_cnt = 1;
swap(pc, pd);
pd -= es;
}
pc -= es;
}
if (pb > pc)
break;
swap(pb, pc);
swap_cnt = 1;
pb += es;
pc -= es;
}
if (swap_cnt == 0) { /* Switch to insertion sort */
for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es)
for (pl = pm;
pl > (char *)a && CMP(thunk, pl - es, pl) > 0;
pl -= es)
swap(pl, pl - es);
return;
}
pn = (char *)a + n * es;
r = min(pa - (char *)a, pb - pa);
vecswap((char *)a, pb - r, r);
r = min(pd - pc, pn - pd - es);
vecswap(pb, pn - r, r);
if ((r = pb - pa) > es)
recast_qsort(a, r / es, es, thunk, cmp);
if ((r = pd - pc) > es) {
/* Iterate rather than recurse to save stack space */
a = pn - r;
n = r / es;
goto loop;
}
}

View File

@@ -28,6 +28,9 @@
#ifndef RECAST_C_API_H
#define RECAST_C_API_H
// for size_t
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
@@ -69,16 +72,20 @@ void recast_filterLedgeSpans(const int walkableHeight, const int walkableClimb,
void recast_filterWalkableLowHeightSpans(int walkableHeight, struct recast_heightfield *solid);
void recast_filterLowHangingWalkableObstacles(const int walkableClimb, struct recast_heightfield *solid);
struct recast_compactHeightfield *recast_newCompactHeightfield(void);
void recast_destroyCompactHeightfield(struct recast_compactHeightfield *compactHeightfield);
int recast_buildCompactHeightfield(const int walkableHeight, const int walkableClimb,
unsigned char flags, struct recast_heightfield *hf, struct recast_compactHeightfield *chf);
struct recast_heightfield *hf, struct recast_compactHeightfield *chf);
int recast_erodeWalkableArea(int radius, struct recast_compactHeightfield *chf);
int recast_buildDistanceField(struct recast_compactHeightfield *chf);
int recast_buildRegions(struct recast_compactHeightfield *chf, int walkableRadius, int borderSize,
int recast_buildRegions(struct recast_compactHeightfield *chf, int borderSize,
int minRegionSize, int mergeRegionSize);
/* Contour set */
@@ -119,7 +126,12 @@ float *recast_polyMeshDetailGetVerts(struct recast_polyMeshDetail *mesh, int *nv
unsigned char *recast_polyMeshDetailGetTris(struct recast_polyMeshDetail *mesh, int *ntris);
unsigned short *recast_polyMeshDetailGetMeshes(struct recast_polyMeshDetail *mesh, int *nmeshes);
unsigned int *recast_polyMeshDetailGetMeshes(struct recast_polyMeshDetail *mesh, int *nmeshes);
/* utility function: quick sort reentrant */
typedef int recast_cmp_t(void *ctx, const void *a, const void *b);
void recast_qsort(void *a, size_t n, size_t es, void *thunk, recast_cmp_t *cmp);
#ifdef __cplusplus
}

View File

@@ -328,14 +328,10 @@ struct SortContext
const int* trisToFacesMap;
};
/* XXX: not thread-safe, but it's called only from modifiers stack
which isn't threaded. Anyway, better to avoid this in the future */
static struct SortContext *_qsort_context;
static int compareByData(const void * a, const void * b)
static int compareByData(void *ctx, const void * a, const void * b)
{
return ( _qsort_context->recastData[_qsort_context->trisToFacesMap[*(int*)a]] -
_qsort_context->recastData[_qsort_context->trisToFacesMap[*(int*)b]] );
return (((struct SortContext *)ctx)->recastData[((struct SortContext *)ctx)->trisToFacesMap[*(int*)a]] -
((struct SortContext *)ctx)->recastData[((struct SortContext *)ctx)->trisToFacesMap[*(int*)b]] );
}
int buildNavMeshData(const int nverts, const float* verts,
@@ -367,8 +363,7 @@ int buildNavMeshData(const int nverts, const float* verts,
trisMapping[i]=i;
context.recastData = recastData;
context.trisToFacesMap = trisToFacesMap;
_qsort_context = &context;
qsort(trisMapping, ntris, sizeof(int), compareByData);
recast_qsort(trisMapping, ntris, sizeof(int), &context, compareByData);
//search first valid triangle - triangle of convex polygon
validTriStart = -1;

View File

@@ -526,7 +526,7 @@ Scene *add_scene(const char *name)
sce->gm.recastData.agentradius = 0.6f;
sce->gm.recastData.edgemaxlen = 12.0f;
sce->gm.recastData.edgemaxerror = 1.3f;
sce->gm.recastData.regionminsize = 50.f;
sce->gm.recastData.regionminsize = 8.f;
sce->gm.recastData.regionmergesize = 20.f;
sce->gm.recastData.vertsperpoly = 6;
sce->gm.recastData.detailsampledist = 6.0f;

View File

@@ -12101,7 +12101,7 @@ static void do_versions(FileData *fd, Library *lib, Main *main)
if(sce->gm.recastData.edgemaxerror == 0.0f)
sce->gm.recastData.edgemaxerror = 1.3f;
if(sce->gm.recastData.regionminsize == 0.0f)
sce->gm.recastData.regionminsize = 50.f;
sce->gm.recastData.regionminsize = 8.f;
if(sce->gm.recastData.regionmergesize == 0.0f)
sce->gm.recastData.regionmergesize = 20.f;
if(sce->gm.recastData.vertsperpoly<3)

View File

@@ -174,7 +174,7 @@ static int buildNavMesh(const RecastData *recastParams, int nverts, float *verts
struct recast_compactHeightfield* chf;
struct recast_contourSet *cset;
int width, height, walkableHeight, walkableClimb, walkableRadius;
int minRegionSize, mergeRegionSize, maxEdgeLen;
int minRegionArea, mergeRegionArea, maxEdgeLen;
float detailSampleDist, detailSampleMaxError;
recast_calcBounds(verts, nverts, bmin, bmax);
@@ -183,8 +183,8 @@ static int buildNavMesh(const RecastData *recastParams, int nverts, float *verts
walkableHeight= (int)ceilf(recastParams->agentheight/ recastParams->cellheight);
walkableClimb= (int)floorf(recastParams->agentmaxclimb / recastParams->cellheight);
walkableRadius= (int)ceilf(recastParams->agentradius / recastParams->cellsize);
minRegionSize= (int)(recastParams->regionminsize * recastParams->regionminsize);
mergeRegionSize= (int)(recastParams->regionmergesize * recastParams->regionmergesize);
minRegionArea= (int)(recastParams->regionminsize * recastParams->regionminsize);
mergeRegionArea= (int)(recastParams->regionmergesize * recastParams->regionmergesize);
maxEdgeLen= (int)(recastParams->edgemaxlen/recastParams->cellsize);
detailSampleDist= recastParams->detailsampledist< 0.9f ? 0 :
recastParams->cellsize * recastParams->detailsampledist;
@@ -212,13 +212,14 @@ static int buildNavMesh(const RecastData *recastParams, int nverts, float *verts
MEM_freeN(triflags);
/* ** Step 3: Filter walkables surfaces ** */
recast_filterLowHangingWalkableObstacles(walkableClimb, solid);
recast_filterLedgeSpans(walkableHeight, walkableClimb, solid);
recast_filterWalkableLowHeightSpans(walkableHeight, solid);
/* ** Step 4: Partition walkable surface to simple regions ** */
chf= recast_newCompactHeightfield();
if(!recast_buildCompactHeightfield(walkableHeight, walkableClimb, RECAST_WALKABLE, solid, chf)) {
if(!recast_buildCompactHeightfield(walkableHeight, walkableClimb, solid, chf)) {
recast_destroyHeightfield(solid);
recast_destroyCompactHeightfield(chf);
@@ -226,6 +227,13 @@ static int buildNavMesh(const RecastData *recastParams, int nverts, float *verts
}
recast_destroyHeightfield(solid);
solid = NULL;
if (!recast_erodeWalkableArea(walkableRadius, chf)) {
recast_destroyCompactHeightfield(chf);
return 0;
}
/* Prepare for region partitioning, by calculating distance field along the walkable surface */
if(!recast_buildDistanceField(chf)) {
@@ -235,7 +243,7 @@ static int buildNavMesh(const RecastData *recastParams, int nverts, float *verts
}
/* Partition the walkable surface into simple regions without holes */
if(!recast_buildRegions(chf, walkableRadius, 0, minRegionSize, mergeRegionSize)) {
if(!recast_buildRegions(chf, 0, minRegionArea, mergeRegionArea)) {
recast_destroyCompactHeightfield(chf);
return 0;
@@ -293,7 +301,8 @@ static Object* createRepresentation(bContext *C, struct recast_polyMesh *pmesh,
Object* obedit;
int createob= base==NULL;
int nverts, nmeshes, nvp;
unsigned short *verts, *meshes, *polys;
unsigned short *verts, *polys;
unsigned int *meshes;
float bmin[3], cs, ch, *dverts;
unsigned char *tris;
ModifierData *md;
@@ -348,7 +357,7 @@ static Object* createRepresentation(bContext *C, struct recast_polyMesh *pmesh,
for(i= 0; i<nmeshes; i++) {
int uniquevbase= em->totvert;
unsigned short vbase= meshes[4*i+0];
unsigned int vbase= meshes[4*i+0];
unsigned short ndv= meshes[4*i+1];
unsigned short tribase= meshes[4*i+2];
unsigned short trinum= meshes[4*i+3];

View File

@@ -518,7 +518,7 @@ void KX_NavMeshObject::DrawNavMesh(NavMeshRenderMode renderMode)
else
v = m_navMesh->getDetailVertex(pd->vbase+(t[k]-p->nv));
float pos[3];
vcopy(pos, v);
rcVcopy(pos, v);
flipAxes(pos);
tri[k].setValue(pos);
}

View File

@@ -325,9 +325,9 @@ inline float vdot2(const float* a, const float* b)
static bool barDistSqPointToTri(const float* p, const float* a, const float* b, const float* c)
{
float v0[3], v1[3], v2[3];
vsub(v0, c,a);
vsub(v1, b,a);
vsub(v2, p,a);
rcVsub(v0, c,a);
rcVsub(v1, b,a);
rcVsub(v2, p,a);
const float dot00 = vdot2(v0, v0);
const float dot01 = vdot2(v0, v1);