void rcFilterLowHangingWalkableObstacles(rcContext* ctx, const int walkableClimb, rcHeightfield& solid) { rcAssert(ctx); 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; 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 = RC_NULL_AREA; } // Copy walkable flag so that it cannot propagate // past multiple non-walkable objects. previousWalkable = walkable; } } } ctx->stopTimer(RC_TIMER_FILTER_LOW_OBSTACLES); }
void rcMergeSpans( rcContext* ctx, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); const int w = solid.width; const int h = solid.height; for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { for( rcSpan* s = solid.spans[x + y*w]; s != NULL && s->next != NULL; s = s->next ) { if( !rcIsSimilarTypeArea( s->area, s->next->area ) && rcAbs( static_cast<int>( s->next->smin ) - static_cast<int>( s->smax ) ) <= 2 ) { // merge rcSpan* next = s->next; s->smax = next->smax; const bool walkable = rcIsWalkableArea( s->area ) || rcIsWalkableArea( next->area ); s->area = next->area; s->area &= walkable ? ~RC_UNWALKABLE_AREA : ~RC_WALKABLE_AREA; s->area |= walkable ? RC_WALKABLE_AREA : RC_UNWALKABLE_AREA; s->next = next->next; freeSpan( solid, next ); } } } } ctx->stopTimer( RC_TIMER_TEMPORARY ); }
/// @par /// /// Spans will only be added for triangles that overlap the heightfield grid. /// /// @see rcHeightfield bool rcRasterizeTriangles(rcContext* ctx, const float* verts, const unsigned char* areas, const int nt, rcHeightfield& solid, const int flagMergeThr) { rcAssert(ctx); rcScopedTimer timer(ctx, 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. if (!rasterizeTri(v0, v1, v2, areas[i], solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) { ctx->log(RC_LOG_ERROR, "rcRasterizeTriangles: Out of memory."); return false; } } return true; }
/// @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 navAreaMask areaMask, const int flagMergeThr ) { rcAssert(ctx); addSpan(hf, x,y, smin, smax, areaMask, flagMergeThr); }
void rcMarkWalkableLowHangingObstacles( rcContext* ctx, const int walkableClimb, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); 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; for( rcSpan* s = solid.spans[x + y*w]; s != NULL; ps = s, s = s->next ) { const bool walkable = rcIsWalkableArea( s->area ); if( !walkable && previousWalkable ) { if( rcAbs((int)s->smax - (int)ps->smax) <= walkableClimb ) { s->area &= ~RC_UNWALKABLE_AREA; s->area |= RC_WALKABLE_AREA; } } previousWalkable = walkable; } } } ctx->stopTimer( RC_TIMER_TEMPORARY ); }
void rcFilterWalkableLowHeightSpans(rcContext* ctx, int walkableHeight, rcHeightfield& solid) { rcAssert(ctx); ctx->startTimer(RC_TIMER_FILTER_WALKABLE); const int w = solid.width; const int h = solid.height; const int MAX_HEIGHT = 0xffff; // Remove walkable flag from spans which do not have enough // space above them for the agent to stand there. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { 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; if ((top - bot) <= walkableHeight) s->area = RC_NULL_AREA; } } } ctx->stopTimer(RC_TIMER_FILTER_WALKABLE); }
void rcMarkTerrainWalkableUnderFloorSpans( rcContext* ctx, const int walkableClimb, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); const int w = solid.width; const int h = solid.height; ////////////////////////////////////////////////////////////////////////// for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { rcMarkWalkableLowerFloorSpan( x, y, walkableClimb, solid ); } } for( int x = 0; x < w; ++x ) { for( int y = 0; y < h; ++y ) { rcMarkWalkableLowerFloorSpan( x, y, walkableClimb, solid ); } } for( int y = h-1; 0 <= y; --y ) { for( int x = w-1; 0 <= x; --x ) { rcMarkWalkableLowerFloorSpan( x, y, walkableClimb, solid ); } } for( int x = w-1; 0 <= x; --x ) { for( int y = h-1; 0 <= y; --y ) { rcMarkWalkableLowerFloorSpan( x, y, walkableClimb, solid ); } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { rcMarkLedgeJumpableLowerFloorSpan( x, y, walkableClimb, solid ); } } for( int x = 0; x < w; ++x ) { for( int y = 0; y < h; ++y ) { rcMarkLedgeJumpableLowerFloorSpan( x, y, walkableClimb, solid ); } } for( int y = h-1; 0 <= y; --y ) { for( int x = w-1; 0 <= x; --x ) { rcMarkLedgeJumpableLowerFloorSpan( x, y, walkableClimb, solid ); } } for( int x = w-1; 0 <= x; --x ) { for( int y = h-1; 0 <= y; --y ) { rcMarkLedgeJumpableLowerFloorSpan( x, y, walkableClimb, solid ); } } ////////////////////////////////////////////////////////////////////////// ctx->stopTimer( RC_TIMER_TEMPORARY ); }
/// @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::doResize(int n) { if (!m_cap) m_cap = n; while (m_cap < n) m_cap *= 2; int* newData = (int*)rcAlloc(m_cap*sizeof(int), RC_ALLOC_TEMP); rcAssert(newData); if (m_size && newData) memcpy(newData, m_data, m_size*sizeof(int)); rcFree(m_data); m_data = newData; }
/// @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) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD); if (chf.dist) { 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* dst = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); if (!dst) { ctx->log(RC_LOG_ERROR, "rcBuildDistanceField: Out of memory 'dst' (%d).", chf.spanCount); rcFree(src); return false; } unsigned short maxDist = 0; ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST); calculateDistanceField(chf, src, maxDist); chf.maxDistance = maxDist; ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_DIST); ctx->startTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR); // Blur if (boxBlur(chf, 1, src, dst) != src) rcSwap(src, dst); // Store distance. chf.dist = src; ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD_BLUR); ctx->stopTimer(RC_TIMER_BUILD_DISTANCEFIELD); rcFree(dst); return true; }
/// @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. bool rcAddSpan(rcContext* ctx, rcHeightfield& hf, const int x, const int y, const unsigned short smin, const unsigned short smax, const unsigned char area, const int flagMergeThr) { rcAssert(ctx); if (!addSpan(hf, x, y, smin, smax, area, flagMergeThr)) { ctx->log(RC_LOG_ERROR, "rcAddSpan: Out of memory."); return false; } return true; }
/// @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) { 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, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr); ctx->stopTimer(RC_TIMER_RASTERIZE_TRIANGLES); }
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) { chf.areas[i] = areaId; } } } } ctx->stopTimer(RC_TIMER_MARK_BOX_AREA); }
/// @par /// /// No spans will be added if the triangle does not overlap the heightfield grid. /// /// @see rcHeightfield bool rcRasterizeTriangle(rcContext* ctx, const float* v0, const float* v1, const float* v2, const unsigned char area, rcHeightfield& solid, const int flagMergeThr) { rcAssert(ctx); rcScopedTimer timer(ctx, RC_TIMER_RASTERIZE_TRIANGLES); const float ics = 1.0f/solid.cs; const float ich = 1.0f/solid.ch; if (!rasterizeTri(v0, v1, v2, area, solid, solid.bmin, solid.bmax, solid.cs, ics, ich, flagMergeThr)) { ctx->log(RC_LOG_ERROR, "rcRasterizeTriangle: Out of memory."); return false; } return true; }
void rcFilterUnwalkableLedgeSpans( rcContext* ctx, const int /*walkableHeight*/, const int walkableClimb, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); const int w = solid.width; const int h = solid.height; for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { for( rcSpan* s = solid.spans[x + y*w]; s != NULL; s = s->next ) { if( !rcCanMovableArea( s->area ) || !rcIsObjectArea( s->area ) ) { continue; } const int smax = static_cast<int>( s->smax ); int connectedEdgeCount = 0; for( int dir = 0; dir < 4; ++dir ) { int dx = x + rcGetDirOffsetX(dir); int dy = y + rcGetDirOffsetY(dir); if( dx < 0 || dy < 0 || dx >= w || dy >= h ) { ++connectedEdgeCount; continue; } for( rcSpan* ns = solid.spans[dx + dy*w]; ns != NULL; ns = ns->next ) { const int nsmax = static_cast<int>( ns->smax ); if( rcAbs( smax - nsmax ) <= walkableClimb*0.25f ) { ++connectedEdgeCount; } } } if( connectedEdgeCount < 2 ) { s->area = RC_NULL_AREA; } } } } ctx->stopTimer( RC_TIMER_TEMPORARY ); }
void rcMarkLevelSpans( rcContext* ctx, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); const int w = solid.width; const int h = solid.height; for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { int level = 0; for( rcSpan* s = solid.spans[x + y*w]; s != NULL; s = s->next ) { s->level = level++; } } } ctx->stopTimer( RC_TIMER_TEMPORARY ); }
void rcFilterUnderFloorObjectSpans( rcContext* ctx, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); const int w = solid.width; const int h = solid.height; for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { for( rcSpan* s = solid.spans[x + y*w]; s != NULL && s->next != NULL; s = s->next ) { if( rcCanMovableArea( s->area ) /*&& rcCanMovableArea( s->next->area )*/ ) { s->area |= RC_UNDER_FLOOR_AREA; } } } } ctx->stopTimer( RC_TIMER_TEMPORARY ); }
void rcFilterUnwalkableAreaSpans( rcContext* ctx, const unsigned char area, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); const int w = solid.width; const int h = solid.height; for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { for( rcSpan* s = solid.spans[x + y*w]; s; s = s->next ) { if( s->area & area ) { s->area = RC_NULL_AREA; } } } } ctx->stopTimer( RC_TIMER_TEMPORARY ); }
/// @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); }
bool rcBuildHeightfieldLayersChunky(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, const int chunkSize, rcHeightfieldLayerSet& lset) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_LAYERS); rcScopedDelete<unsigned short> srcReg = (unsigned short*)rcAlloc(sizeof(unsigned short)*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 short)*chf.spanCount); rcLayerRegionMonotone* regs = NULL; int nregs = 0; const bool bHasRegions = CollectLayerRegionsChunky(ctx, chf, borderSize, chunkSize, srcReg, regs, nregs); if (!bHasRegions) { return false; } const bool bHasSaved = SplitAndStoreLayerRegions(ctx, chf, borderSize, walkableHeight, srcReg, regs, nregs, lset); rcFree(regs); if (!bHasSaved) { return false; } ctx->stopTimer(RC_TIMER_BUILD_LAYERS); 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); rcScopedDelete<unsigned short> spanBuf4 = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP); if (!spanBuf4) { ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'spanBuf4' (%d).", chf.spanCount*4); return false; } ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); unsigned short* srcReg = spanBuf4; if (!rcGatherRegionsNoFilter(ctx, chf, borderSize, spanBuf4)) return false; ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); // Filter out small regions. const int chunkSize = rcMax(chf.width, chf.height); if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chunkSize, chf.maxRegions, chf, srcReg)) return false; ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); // Write the result out. for (int i = 0; i < chf.spanCount; ++i) chf.spans[i].reg = srcReg[i]; ctx->stopTimer(RC_TIMER_BUILD_REGIONS); return true; }
void rcFilterUnwalkableLowHeightSpans( rcContext* ctx, const int walkableHeight, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); const int w = solid.width; const int h = solid.height; for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { for( rcSpan* s = solid.spans[x + y*w]; s != NULL && s->next != NULL; s = s->next ) { const int bot = static_cast<int>( s->smax ); const int top = static_cast<int>( s->next->smin ); if( ( top - bot ) <= walkableHeight ) { s->area = RC_NULL_AREA; } } } } ctx->stopTimer( RC_TIMER_TEMPORARY ); }
void rcMarkSideLedgeSpans( rcContext* ctx, const int walkableClimb, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); const int w = solid.width; const int h = solid.height; for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { for( rcSpan* s = solid.spans[x + y*w]; s != NULL; s = s->next ) { if( !rcIsObjectArea( s->area ) || !rcIsWalkableObjectArea( s->area ) ) { continue; } for( int dir = 0; dir < 4; ++dir ) { const int dx = x + rcGetDirOffsetX(dir); const int dy = y + rcGetDirOffsetY(dir); if( dx < 0 || dy < 0 || dx >= w || dy >= h ) { continue; } for( rcSpan* ns = solid.spans[dx + dy*w]; ns != NULL; ns = ns->next ) { if( (ns->area & RC_UNWALKABLE_AREA) == RC_UNWALKABLE_AREA ) { const int gap = rcAbs( static_cast<int>(s->smax) - static_cast<int>(ns->smax) ); if( gap <= walkableClimb ) { s->area |= RC_CLIMBABLE_AREA; } } } } } } } ctx->stopTimer( RC_TIMER_TEMPORARY ); }
void rcFilterSpans( rcContext* ctx, const int /*walkableHeight*/, const int /*walkableClimb*/, rcHeightfield& solid ) { rcAssert( ctx ); ctx->startTimer( RC_TIMER_TEMPORARY ); const int w = solid.width; const int h = solid.height; for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { for( rcSpan* s = solid.spans[x + y*w]; s != NULL; s = s->next ) { if( (s->area & RC_UNWALKABLE_AREA) == RC_UNWALKABLE_AREA ) { s->area = RC_NULL_AREA; } if( s->next != NULL && rcIsTerrainArea( s->next->area ) ) { s->area = RC_NULL_AREA; } ////////////////////////////////////////////////////////////////////////// // temporary if( !rcIsObjectArea( s->area ) && (s->area & RC_UNDER_FLOOR_AREA) == RC_UNDER_FLOOR_AREA ) { s->area = RC_NULL_AREA; } ////////////////////////////////////////////////////////////////////////// if( rcCanMovableArea( s->area ) ) { s->area |= RC_TERRAIN_AREA; } } } } ctx->stopTimer( RC_TIMER_TEMPORARY ); }
bool rcBuildPolyMesh(rcContext* ctx, rcContourSet& cset, int nvp, rcPolyMesh& mesh) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_POLYMESH); rcVcopy(mesh.bmin, cset.bmin); rcVcopy(mesh.bmax, cset.bmax); mesh.cs = cset.cs; mesh.ch = cset.ch; 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); } if (maxVertices >= 0xfffe) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Too many vertices %d.", maxVertices); return false; } rcScopedDelete<unsigned char> vflags = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxVertices, RC_ALLOC_TEMP); if (!vflags) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices); return false; } memset(vflags, 0, maxVertices); mesh.verts = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertices*3, RC_ALLOC_PERM); if (!mesh.verts) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.verts' (%d).", maxVertices); return false; } mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris*nvp*2*2, RC_ALLOC_PERM); if (!mesh.polys) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'mesh.polys' (%d).", maxTris*nvp*2); return false; } mesh.regs = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxTris, RC_ALLOC_PERM); if (!mesh.regs) { 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); rcScopedDelete<int> nextVert = (int*)rcAlloc(sizeof(int)*maxVertices, RC_ALLOC_TEMP); if (!nextVert) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'nextVert' (%d).", maxVertices); return false; } memset(nextVert, 0, sizeof(int)*maxVertices); rcScopedDelete<int> firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); if (!firstVert) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT); return false; } for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) firstVert[i] = -1; rcScopedDelete<int> indices = (int*)rcAlloc(sizeof(int)*maxVertsPerCont, RC_ALLOC_TEMP); if (!indices) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'indices' (%d).", maxVertsPerCont); return false; } rcScopedDelete<int> tris = (int*)rcAlloc(sizeof(int)*maxVertsPerCont*3, RC_ALLOC_TEMP); if (!tris) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'tris' (%d).", maxVertsPerCont*3); return false; } rcScopedDelete<unsigned short> polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*(maxVertsPerCont+1)*nvp, RC_ALLOC_TEMP); if (!polys) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Out of memory 'polys' (%d).", maxVertsPerCont*nvp); return false; } unsigned short* tmpPoly = &polys[maxVertsPerCont*nvp]; for (int i = 0; i < cset.nconts; ++i) { rcContour& cont = cset.conts[i]; // Skip null contours. if (cont.nverts < 3) continue; // Triangulate contour for (int j = 0; j < cont.nverts; ++j) indices[j] = j; int ntris = triangulate(cont.nverts, cont.verts, &indices[0], &tris[0]); if (ntris <= 0) { // Bad triangulation, should not happen. /* 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]); } 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) { const int* v = &cont.verts[j*4]; indices[j] = addVertex((unsigned short)v[0], (unsigned short)v[1], (unsigned short)v[2], mesh.verts, firstVert, nextVert, mesh.nverts); if (v[3] & RC_BORDER_VERTEX) { // This vertex should be removed. vflags[indices[j]] = 1; } } // Build initial polygons. int npolys = 0; memset(polys, 0xff, maxVertsPerCont*nvp*sizeof(unsigned short)); for (int j = 0; j < ntris; ++j) { int* t = &tris[j*3]; if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) { polys[npolys*nvp+0] = (unsigned short)indices[t[0]]; polys[npolys*nvp+1] = (unsigned short)indices[t[1]]; polys[npolys*nvp+2] = (unsigned short)indices[t[2]]; npolys++; } } if (!npolys) continue; // Merge polygons. if (nvp > 3) { for(;;) { // Find best polygons to merge. int bestMergeVal = 0; int bestPa = 0, bestPb = 0, bestEa = 0, bestEb = 0; for (int j = 0; j < npolys-1; ++j) { unsigned short* pj = &polys[j*nvp]; for (int k = j+1; k < npolys; ++k) { unsigned short* pk = &polys[k*nvp]; int ea, eb; int v = getPolyMergeValue(pj, pk, mesh.verts, ea, eb, nvp); if (v > bestMergeVal) { bestMergeVal = v; bestPa = j; bestPb = k; bestEa = ea; bestEb = eb; } } } if (bestMergeVal > 0) { // Found best, merge. unsigned short* pa = &polys[bestPa*nvp]; unsigned short* pb = &polys[bestPb*nvp]; mergePolys(pa, pb, bestEa, bestEb, tmpPoly, nvp); memcpy(pb, &polys[(npolys-1)*nvp], sizeof(unsigned short)*nvp); npolys--; } else { // Could not merge any polygons, stop. break; } } } // Store polygons. for (int j = 0; j < npolys; ++j) { unsigned short* p = &mesh.polys[mesh.npolys*nvp*2]; unsigned short* q = &polys[j*nvp]; 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; } } } // Remove edge vertices. for (int i = 0; i < mesh.nverts; ++i) { if (vflags[i]) { 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; } } // Calculate adjacency. if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, nvp)) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMesh: Adjacency failed."); return false; } // 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 (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); } ctx->stopTimer(RC_TIMER_BUILD_POLYMESH); return true; }
bool rcMergePolyMeshes(rcContext* ctx, rcPolyMesh** meshes, const int nmeshes, rcPolyMesh& mesh) { rcAssert(ctx); if (!nmeshes || !meshes) return true; ctx->startTimer(RC_TIMER_MERGE_POLYMESH); mesh.nvp = meshes[0]->nvp; mesh.cs = meshes[0]->cs; mesh.ch = meshes[0]->ch; 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) { 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 = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVerts*3, RC_ALLOC_PERM); if (!mesh.verts) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'mesh.verts' (%d).", maxVerts*3); return false; } mesh.npolys = 0; mesh.polys = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys*2*mesh.nvp, RC_ALLOC_PERM); if (!mesh.polys) { 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 = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxPolys, RC_ALLOC_PERM); if (!mesh.regs) { 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); rcScopedDelete<int> nextVert = (int*)rcAlloc(sizeof(int)*maxVerts, RC_ALLOC_TEMP); if (!nextVert) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'nextVert' (%d).", maxVerts); return false; } memset(nextVert, 0, sizeof(int)*maxVerts); rcScopedDelete<int> firstVert = (int*)rcAlloc(sizeof(int)*VERTEX_BUCKET_COUNT, RC_ALLOC_TEMP); if (!firstVert) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'firstVert' (%d).", VERTEX_BUCKET_COUNT); return false; } for (int i = 0; i < VERTEX_BUCKET_COUNT; ++i) firstVert[i] = -1; rcScopedDelete<unsigned short> vremap = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxVertsPerMesh, RC_ALLOC_PERM); if (!vremap) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Out of memory 'vremap' (%d).", maxVertsPerMesh); return false; } memset(nextVert, 0, sizeof(int)*maxVerts); for (int i = 0; i < nmeshes; ++i) { const rcPolyMesh* pmesh = meshes[i]; const unsigned short ox = (unsigned short)floorf((pmesh->bmin[0]-mesh.bmin[0])/mesh.cs+0.5f); const unsigned short oz = (unsigned short)floorf((pmesh->bmin[2]-mesh.bmin[2])/mesh.cs+0.5f); for (int j = 0; j < pmesh->nverts; ++j) { unsigned short* v = &pmesh->verts[j*3]; vremap[j] = addVertex(v[0]+ox, v[1], v[2]+oz, mesh.verts, firstVert, nextVert, mesh.nverts); } for (int j = 0; j < pmesh->npolys; ++j) { 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] == RC_MESH_NULL_IDX) break; tgt[k] = vremap[src[k]]; } } } // Calculate adjacency. if (!buildMeshAdjacency(mesh.polys, mesh.npolys, mesh.nverts, mesh.nvp)) { ctx->log(RC_LOG_ERROR, "rcMergePolyMeshes: Adjacency failed."); return false; } 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); } ctx->stopTimer(RC_TIMER_MERGE_POLYMESH); 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; rcScopedDelete<unsigned short> buf = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP); if (!buf) { ctx->log(RC_LOG_ERROR, "rcBuildRegions: Out of memory 'tmp' (%d).", chf.spanCount*4); return false; } ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); rcIntArray stack(1024); rcIntArray visited(1024); 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(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; // 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; 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 > 0) { level = level >= 2 ? level-2 : 0; ctx->startTimer(RC_TIMER_BUILD_REGIONS_EXPAND); // Expand current regions until no empty connected cells found. if (expandRegions(expandIters, level, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_EXPAND); ctx->startTimer(RC_TIMER_BUILD_REGIONS_FLOOD); // Mark new regions with IDs. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { 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.dist[i] < level || srcReg[i] != 0 || chf.areas[i] == RC_NULL_AREA) continue; if (floodRegion(x, y, i, level, regionId, chf, srcReg, srcDist, stack)) regionId++; } } } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FLOOD); } // Expand current regions until no empty connected cells found. if (expandRegions(expandIters*8, 0, chf, srcReg, srcDist, dstReg, dstDist, stack) != srcReg) { rcSwap(srcReg, dstReg); rcSwap(srcDist, dstDist); } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); // Filter out small regions. chf.maxRegions = regionId; if (!filterSmallRegions(ctx, minRegionArea, mergeRegionArea, chf.maxRegions, chf, srcReg)) return false; ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); // Write the result out. for (int i = 0; i < chf.spanCount; ++i) chf.spans[i].reg = srcReg[i]; ctx->stopTimer(RC_TIMER_BUILD_REGIONS); return true; }
/// @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; }
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); }
bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, rcHeightfield& hf, rcCompactHeightfield& chf) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); const int w = hf.width; const int h = hf.height; const int spanCount = rcGetHeightFieldSpanCount(ctx, hf); // Fill in header. chf.width = w; chf.height = h; chf.spanCount = spanCount; chf.walkableHeight = walkableHeight; chf.walkableClimb = walkableClimb; chf.maxRegions = 0; 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 = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); if (!chf.cells) { 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 = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); if (!chf.spans) { 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; // Fill in cells and spans. int idx = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcSpan* s = hf.spans[x + y*w]; // If there are no spans at this cell, just leave the data to index=0, count=0. if (!s) continue; rcCompactCell& c = chf.cells[x + y*w]; c.index = idx; c.count = 0; while (s) { 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++; } s = s->next; } } } // 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) { const rcCompactCell& c = chf.cells[x + y*w]; 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) { 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]; for (int k = (int)nc.index, nk = (int)(nc.index + nc.count); k < nk; ++k) { const rcCompactSpan& ns = chf.spans[k]; const int bot = rcMax(s.y, ns.y); const int top = rcMin(s.y + s.h, ns.y + ns.h); // Check that the gap between the spans is walkable, // and that the climb height between the gaps is not too high. if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) { // Mark direction as walkable. const int b = k - (int)nc.index; if (b < 0 || b > MAX_LAYERS) { tooHighNeighbour = rcMax(tooHighNeighbour, b); continue; } rcSetCon(s, dir, b); break; } } } } } } 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; }
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) { const rcCompactSpan& s = chf.spans[i]; int nc = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) 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; }