/// @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; }
static void CacheLine(WCHAR *lptr, char *xbuf) { if (inSymFile) { char *p = strchr(xbuf, '#'); if (p) return; } if ((incldepth == 0 && (!*lptr || *lptr == '#')) || inSymFile) { char *s = rcStrdup(xbuf); LIST *x = rcAlloc(sizeof(LIST)); x->data = s; if (inSymFile) { *hCachedLineTail = x; hCachedLineTail = &(*hCachedLineTail)->next; } else { *cachedLineTail = x; cachedLineTail = &(*cachedLineTail)->next; } } }
static rcSpan* allocSpan(rcHeightfield& hf) { // If running out of memory, allocate new page and update the freelist. if (!hf.freelist || !hf.freelist->next) { // Create new page. // Allocate memory for the new pool. rcSpanPool* pool = (rcSpanPool*)rcAlloc(sizeof(rcSpanPool), RC_ALLOC_PERM); if (!pool) return 0; // Add the pool into the list of pools. pool->next = hf.pools; hf.pools = pool; // Add new items to the free list. rcSpan* freelist = hf.freelist; rcSpan* head = &pool->items[0]; rcSpan* it = &pool->items[RC_SPANS_PER_POOL]; do { --it; it->next = freelist; freelist = it; } while (it != head); hf.freelist = it; } // Pop item from in front of the free list. rcSpan* it = hf.freelist; hf.freelist = hf.freelist->next; return it; }
/// @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; 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); #if EPIC_ADDITION_USE_NEW_RECAST_RASTERIZER hf.EdgeHits = (rcEdgeHit*)rcAlloc(sizeof(rcEdgeHit) * (hf.height + 1), RC_ALLOC_PERM); memset(hf.EdgeHits, 0, sizeof(rcEdgeHit) * (hf.height + 1)); hf.RowExt = (rcRowExt*)rcAlloc(sizeof(rcRowExt) * (hf.height + 2), RC_ALLOC_PERM); for (int i = 0; i < hf.height + 2; i++) { hf.RowExt[i].MinCol = hf.width + 2; hf.RowExt[i].MaxCol = -2; } hf.tempspans = (rcTempSpan*)rcAlloc(sizeof(rcTempSpan)*(hf.width + 2) * (hf.height + 2), RC_ALLOC_PERM); for (int i = 0; i < hf.height + 2; i++) { for (int j = 0; j < hf.width + 2; j++) { hf.tempspans[i * (hf.width + 2) + j].sminmax[0] = 32000; hf.tempspans[i * (hf.width + 2) + j].sminmax[1] = -32000; } } #endif return true; }
static void DoInsert(struct resRes *menuData, MENUITEM **items, int code, MENUITEM *parent) { switch (code) { MENUITEM *newItem; case IDM_DELETE: UndoDelete(menuData, items); (*items)->expanded = FALSE; *items = (*items)->next; ResSetDirty(menuData); InvalidateRect(menuData->activeHwnd, 0, FALSE); break; case IDM_INSERT: ResGetHeap(workArea, menuData); newItem = rcAlloc(sizeof(MENUITEM)); newItem->next = *items; *items = newItem; StringAsciiToWChar(&newItem->text, "New Item", 8); if (parent) { newItem->id = parent->id; parent->id = NULL; } else { newItem->id = ResAllocateMenuId(); } UndoInsert(menuData, items); ResSetDirty(menuData); InvalidateRect(menuData->activeHwnd, 0, FALSE); break; case IDM_INSERT_SEPARATOR: ResGetHeap(workArea, menuData); newItem = rcAlloc(sizeof(MENUITEM)); newItem->next = *items; SetSeparatorFlag(menuData->resource->u.menu, newItem, TRUE); *items = newItem; UndoInsert(menuData, items); ResSetDirty(menuData); InvalidateRect(menuData->activeHwnd, 0, FALSE); break; } }
/// @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(rcCompactHeightfield& chf) { 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) { return false; } unsigned short* dst = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount, RC_ALLOC_TEMP); if (!dst) { rcFree(src); return false; } unsigned short maxDist = 0; calculateDistanceField(chf, src, maxDist); chf.maxDistance = maxDist; // Blur if (boxBlur(chf, 1, src, dst) != src) rcSwap(src, dst); // Store distance. chf.dist = src; rcFree(dst); return true; }
bool rcCreateHeightfield(rcHeightfield& hf, int width, int height, const float* bmin, const float* bmax, float cs, float ch) { hf.width = width; hf.height = height; 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); return true; }
/// @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 dtCoordinates& bmin, const dtCoordinates& bmax, float cs, float ch) { rcIgnoreUnused(ctx); hf.width = width; hf.height = height; 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); return true; }
static bool mergeContours(rcContour& ca, rcContour& cb, int ia, int ib) { const int maxVerts = ca.nverts + cb.nverts + 2; int* verts = (int*)rcAlloc(sizeof(int)*maxVerts*4, RC_ALLOC_PERM); if (!verts) return false; int nv = 0; // Copy contour A. for (int i = 0; i <= ca.nverts; ++i) { int* dst = &verts[nv*4]; const int* src = &ca.verts[((ia+i)%ca.nverts)*4]; dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; nv++; } // Copy contour B for (int i = 0; i <= cb.nverts; ++i) { int* dst = &verts[nv*4]; const int* src = &cb.verts[((ib+i)%cb.nverts)*4]; dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; nv++; } rcFree(ca.verts); ca.verts = verts; ca.nverts = nv; rcFree(cb.verts); cb.verts = 0; cb.nverts = 0; return true; }
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; 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); return true; }
static void StringTableInsert(struct resRes *stringTableData, int index) { if (index != -1) { STRINGS *strings = rcAlloc(sizeof(STRINGS)); ResGetHeap(workArea, stringTableData); if (strings) { int origIndex = 0; STRINGS **p = &stringTableData->resource->u.stringtable; strings->id = ResAllocateStringId(); strings->length = StringAsciiToWChar(&strings->string, " ", 1); for ( ; index && *p; index--, origIndex++, p = &(*p)->next); strings->next = *p; *p = strings; PopulateItem(stringTableData->gd.childWindow, strings, origIndex); StringTableSetInserted(stringTableData, origIndex); } } }
void NavMeshCreator::allocIntermediateResults() { if (m_success) { if ((m_intermediateContourSet = rcAllocContourSet()) == 0) { m_context->log(RC_LOG_ERROR, "NavMeshCreator: unable to allocate memory for the contour set."); m_success = false; } else if ((m_intermediatePolyMesh = rcAllocPolyMesh()) == 0) { m_context->log(RC_LOG_ERROR, "NavMeshCreator: unable to allocate memory for the polygon mesh."); m_success = false; } else if ((m_intermediateHeightfield = rcAllocHeightfield()) == 0) { m_context->log(RC_LOG_ERROR, "NavMeshCreator: unable to allocate memory for the heightfield."); m_success = false; } else if ((m_intermediateCompactHeightfield = rcAllocCompactHeightfield()) == 0) { m_context->log(RC_LOG_ERROR, "NavMeshCreator: unable to allocate memory for the compact heightfield."); m_success = false; } else if ((m_intermediatePolyMeshDetail = rcAllocPolyMeshDetail()) == 0) { m_context->log(RC_LOG_ERROR, "NavMeshCreator: unable to allocate memory for the detail polygon mesh."); m_success = false; } else if ((m_intermediateTriangleTags = static_cast<unsigned char*>(rcAlloc(sizeof(unsigned char) * m_inputTrianglesCount,RC_ALLOC_TEMP))) == 0) { m_context->log(RC_LOG_ERROR, "NavMeshCreator: unable to allocate memory for the triangle tags."); m_success = false; } else if ((m_intermediateNavMeshCreateParams = static_cast<dtNavMeshCreateParams*>(rcAlloc(sizeof(dtNavMeshCreateParams),RC_ALLOC_TEMP)))==0) { m_context->log(RC_LOG_ERROR, "NavMeshCreator: unable to allocate memory for the Detour navmesh creation parameters."); m_success = false; } } }
void PropSetIdName(struct resRes *data, char *buf, EXPRESSION **exp, CONTROL *ctls) { WCHAR name[512], *p = name; char xx[256],*q; int id = GetId(*exp); while (isspace(*buf)) buf ++; q = buf; if (!isalpha(*q) && *q != '_') return; while (*q) { if (!isalnum(*q) && (*q) != '_') return; q++; } ResGetHeap(workArea, data); ResSetDirty(data); if (!ctls) ResSetTreeName(data,buf); while (*buf) *p++ = *buf++; *p = 0; *exp = ReadExpFromString(name); if (!*exp) { *exp = rcAlloc(sizeof(EXPRESSION)); (*exp)->type = e_int; (*exp)->rendition = rcStrdup(name); SetId(data, *exp, id, ctls); } else { sprintf(xx, "%d", (*exp)->val); if (!strcmp(xx, (*exp)->rendition)) (*exp)->rendition = NULL; else SetId(data, *exp, id, ctls); } }
/// @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; }
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; }
rcHeightfield* rcAllocHeightfield() { rcHeightfield* hf = (rcHeightfield*)rcAlloc(sizeof(rcHeightfield), RC_ALLOC_PERM); memset(hf, 0, sizeof(rcHeightfield)); return hf; }
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; }
rcPolyMeshDetail* rcAllocPolyMeshDetail() { rcPolyMeshDetail* dmesh = (rcPolyMeshDetail*)rcAlloc(sizeof(rcPolyMeshDetail), RC_ALLOC_PERM); memset(dmesh, 0, sizeof(rcPolyMeshDetail)); return dmesh; }
rcPolyMesh* rcAllocPolyMesh() { rcPolyMesh* pmesh = (rcPolyMesh*)rcAlloc(sizeof(rcPolyMesh), RC_ALLOC_PERM); memset(pmesh, 0, sizeof(rcPolyMesh)); return pmesh; }
rcContourSet* rcAllocContourSet() { rcContourSet* cset = (rcContourSet*)rcAlloc(sizeof(rcContourSet), RC_ALLOC_PERM); memset(cset, 0, sizeof(rcContourSet)); return cset; }
/// @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; }
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; }
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]; const int nv = countPolyVerts(p, nvp); bool hasRem = false; for (int j = 0; j < nv; ++j) if (p[j] == rem) hasRem = true; if (hasRem) { // Collect edges which does not touch the removed vertex. for (int j = 0, k = nv-1; j < nv; k = j++) { if (p[j] != rem && p[k] != rem) { 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; } } // Remove vertex. for (int i = (int)rem; i < mesh.nverts; ++i) { mesh.verts[i*3+0] = mesh.verts[(i+1)*3+0]; mesh.verts[i*3+1] = mesh.verts[(i+1)*3+1]; mesh.verts[i*3+2] = mesh.verts[(i+1)*3+2]; } mesh.nverts--; // Adjust indices to match the removed vertex layout. 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) p[j]--; } for (int i = 0; i < nedges; ++i) { 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; // 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) { bool match = false; for (int i = 0; i < nedges; ++i) { 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) { // 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; } } if (!match) break; } rcScopedDelete<int> tris = (int*)rcAlloc(sizeof(int)*nhole*3, RC_ALLOC_TEMP); if (!tris) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tris' (%d).", nhole*3); return false; } rcScopedDelete<int> tverts = (int*)rcAlloc(sizeof(int)*nhole*4, RC_ALLOC_TEMP); if (!tverts) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'tverts' (%d).", nhole*4); return false; } rcScopedDelete<int> thole = (int*)rcAlloc(sizeof(int)*nhole, RC_ALLOC_TEMP); if (!tverts) { ctx->log(RC_LOG_WARNING, "removeVertex: Out of memory 'thole' (%d).", nhole); return false; } // Generate temp vertex array for triangulation. for (int i = 0; i < nhole; ++i) { const int pi = hole[i]; tverts[i*4+0] = mesh.verts[pi*3+0]; tverts[i*4+1] = mesh.verts[pi*3+1]; tverts[i*4+2] = mesh.verts[pi*3+2]; tverts[i*4+3] = 0; thole[i] = i; } // Triangulate the hole. int ntris = triangulate(nhole, &tverts[0], &thole[0], tris); if (ntris < 0) { ntris = -ntris; ctx->log(RC_LOG_WARNING, "removeVertex: triangulate() returned bad results."); } // 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) { int* t = &tris[j*3]; if (t[0] != t[1] && t[0] != t[2] && t[1] != t[2]) { polys[npolys*nvp+0] = (unsigned short)hole[t[0]]; polys[npolys*nvp+1] = (unsigned short)hole[t[1]]; polys[npolys*nvp+2] = (unsigned short)hole[t[2]]; pregs[npolys] = (unsigned short)hreg[t[0]]; pareas[npolys] = (unsigned char)harea[t[0]]; npolys++; } } if (!npolys) return true; // 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); pregs[bestPb] = pregs[npolys-1]; pareas[bestPb] = pareas[npolys-1]; npolys--; } else { // Could not merge any polygons, stop. break; } } } // Store polygons. for (int i = 0; i < npolys; ++i) { if (mesh.npolys >= maxTris) break; unsigned short* p = &mesh.polys[mesh.npolys*nvp*2]; memset(p,0xff,sizeof(unsigned short)*nvp*2); 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; } } return true; }
static bool canRemoveVertex(rcContext* ctx, rcPolyMesh& mesh, const unsigned short rem) { const int nvp = mesh.nvp; // Count number of polygons to remove. 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]; 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 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 = (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 = (rcEdge*)rcAlloc(sizeof(rcEdge)*maxEdgeCount, RC_ALLOC_TEMP); if (!edges) { rcFree(firstEdge); return false; } for (int i = 0; i < nverts; i++) 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) { unsigned short v0 = t[j]; 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]; edge.vert[0] = v0; edge.vert[1] = v1; edge.poly[0] = (unsigned short)i; edge.polyEdge[0] = (unsigned short)j; edge.poly[1] = (unsigned short)i; edge.polyEdge[1] = 0; // Insert edge nextEdge[edgeCount] = firstEdge[v0]; firstEdge[v0] = (unsigned short)edgeCount; edgeCount++; } } } for (int i = 0; i < npolys; ++i) { unsigned short* t = &polys[i*vertsPerPoly*2]; for (int j = 0; j < vertsPerPoly; ++j) { unsigned short v0 = t[j]; 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 != RC_MESH_NULL_IDX; e = nextEdge[e]) { rcEdge& edge = edges[e]; if (edge.vert[1] == v0 && edge.poly[0] == edge.poly[1]) { edge.poly[1] = (unsigned short)i; edge.polyEdge[1] = (unsigned short)j; break; } } } } } // Store adjacency for (int i = 0; i < edgeCount; ++i) { const rcEdge& e = edges[i]; if (e.poly[0] != e.poly[1]) { unsigned short* p0 = &polys[e.poly[0]*vertsPerPoly*2]; unsigned short* p1 = &polys[e.poly[1]*vertsPerPoly*2]; p0[vertsPerPoly + e.polyEdge[0]] = e.poly[1]; p1[vertsPerPoly + e.polyEdge[1]] = e.poly[0]; } } rcFree(firstEdge); rcFree(edges); 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; }
rcCompactHeightfield* rcAllocCompactHeightfield() { rcCompactHeightfield* chf = (rcCompactHeightfield*)rcAlloc(sizeof(rcCompactHeightfield), RC_ALLOC_PERM); memset(chf, 0, sizeof(rcCompactHeightfield)); return chf; }
rcHeightfieldLayerSet* rcAllocHeightfieldLayerSet() { rcHeightfieldLayerSet* lset = (rcHeightfieldLayerSet*)rcAlloc(sizeof(rcHeightfieldLayerSet), RC_ALLOC_PERM); memset(lset, 0, sizeof(rcHeightfieldLayerSet)); return lset; }
static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegionSize, unsigned short& maxRegionId, rcCompactHeightfield& chf, unsigned short* srcReg) { const int w = chf.width; const int h = chf.height; const int nreg = maxRegionId+1; rcRegion* regions = (rcRegion*)rcAlloc(sizeof(rcRegion)*nreg, RC_ALLOC_TEMP); if (!regions) { ctx->log(RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (%d).", nreg); return false; } // Construct regions for (int i = 0; i < nreg; ++i) new(®ions[i]) rcRegion((unsigned short)i); // Find edge of a region and find connections around the contour. 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) { unsigned short r = srcReg[i]; if (r == 0 || r >= nreg) continue; rcRegion& reg = regions[r]; reg.spanCount++; // Update floors. for (int j = (int)c.index; j < ni; ++j) { if (i == j) continue; unsigned short floorId = srcReg[j]; if (floorId == 0 || floorId >= nreg) continue; addUniqueFloorRegion(reg, floorId); } // Have found contour 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, srcReg, x, y, i, dir)) { ndir = dir; break; } } if (ndir != -1) { // The cell is at border. // Walk around the contour to find all the neighbours. walkContour(x, y, i, ndir, chf, srcReg, reg.connections); } } } } // 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.spanCount == 0) continue; if (reg.visited) continue; // 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()) { // Pop int ri = stack.pop(); rcRegion& creg = regions[ri]; spanCount += creg.spanCount; trace.push(ri); for (int j = 0; j < creg.connections.size(); ++j) { if (creg.connections[j] & RC_BORDER_REG) { connectsToBorder = true; continue; } rcRegion& neireg = regions[creg.connections[j]]; if (neireg.visited) continue; if (neireg.id == 0 || (neireg.id & RC_BORDER_REG)) continue; // Visit stack.push(neireg.id); neireg.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 { mergeCount = 0; for (int i = 0; i < nreg; ++i) { rcRegion& reg = regions[i]; if (reg.id == 0 || (reg.id & RC_BORDER_REG)) continue; if (reg.spanCount == 0) continue; // Check to see if the region should be merged. 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. int smallest = 0xfffffff; unsigned short mergeId = reg.id; for (int j = 0; j < reg.connections.size(); ++j) { 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.spanCount < smallest && canMergeWithRegion(reg, mreg) && canMergeWithRegion(mreg, reg)) { smallest = mreg.spanCount; mergeId = mreg.id; } } // Found new id. if (mergeId != reg.id) { unsigned short oldId = reg.id; rcRegion& target = regions[mergeId]; // Merge neighbours. if (mergeRegions(target, reg)) { // Fixup regions pointing to current region. for (int j = 0; j < nreg; ++j) { if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG)) continue; // If another region was already merged into current region // change the nid of the previous region too. if (regions[j].id == oldId) regions[j].id = mergeId; // Replace the current region with the new one if the // current regions is neighbour. replaceNeighbour(regions[j], oldId, mergeId); } mergeCount++; } } } } 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. regions[i].remap = true; } unsigned short regIdGen = 0; for (int i = 0; i < nreg; ++i) { if (!regions[i].remap) continue; unsigned short oldId = regions[i].id; unsigned short newId = ++regIdGen; for (int j = i; j < nreg; ++j) { if (regions[j].id == oldId) { regions[j].id = newId; regions[j].remap = false; } } } maxRegionId = regIdGen; // Remap regions. for (int i = 0; i < chf.spanCount; ++i) { if ((srcReg[i] & RC_BORDER_REG) == 0) srcReg[i] = regions[srcReg[i]].id; } for (int i = 0; i < nreg; ++i) regions[i].~rcRegion(); rcFree(regions); return true; }