//---------------------------------------- void ofxBox2dPolygon::simplify(float tolerance) { // simplify the countour DP vertices = simplifyContour(vertices, tolerance); }
/// @par /// /// The raw contours will match the region outlines exactly. The @p maxError and @p maxEdgeLen /// parameters control how closely the simplified contours will match the raw contours. /// /// Simplified contours are generated such that the vertices for portals between areas match up. /// (They are considered mandatory vertices.) /// /// Setting @p maxEdgeLength to zero will disabled the edge length feature. /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocContourSet, rcCompactHeightfield, rcContourSet, rcConfig bool rcBuildContours(rcContext* ctx, rcCompactHeightfield& chf, const float maxError, const int maxEdgeLen, rcContourSet& cset, const int buildFlags) { rcAssert(ctx); const int w = chf.width; const int h = chf.height; const int borderSize = chf.borderSize; ctx->startTimer(RC_TIMER_BUILD_CONTOURS); rcVcopy(cset.bmin, chf.bmin); rcVcopy(cset.bmax, chf.bmax); if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. const float pad = borderSize*chf.cs; cset.bmin[0] += pad; cset.bmin[2] += pad; cset.bmax[0] -= pad; cset.bmax[2] -= pad; } cset.cellSizeXZ = chf.cs; cset.cellSizeY = chf.ch; cset.width = chf.width - chf.borderSize*2; cset.height = chf.height - chf.borderSize*2; cset.borderSize = chf.borderSize; int maxContours = rcMax((int)chf.maxRegions, 8); cset.conts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); if (!cset.conts) return false; cset.nconts = 0; rcScopedDelete<unsigned char> flags = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); if (!flags) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'flags' (%d).", chf.spanCount); return false; } ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); // Mark boundaries. 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 char res = 0; const rcCompactSpan& s = chf.spans[i]; if (!chf.spans[i].regionID || (chf.spans[i].regionID & RC_BORDER_REG)) { flags[i] = 0; continue; } for (int dir = 0; dir < 4; ++dir) { unsigned short r = 0; if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); r = chf.spans[ai].regionID; } if (r == chf.spans[i].regionID) res |= (1 << dir); } flags[i] = res ^ 0xf; // Inverse, mark non connected edges. } } } ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); rcIntArray verts(256); rcIntArray simplified(64); 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 (flags[i] == 0 || flags[i] == 0xf) { flags[i] = 0; continue; } const unsigned short reg = chf.spans[i].regionID; if (!reg || (reg & RC_BORDER_REG)) continue; const navAreaMask areaMask = chf.areaMasks[ i ]; verts.resize(0); simplified.resize(0); ctx->startTimer(RC_TIMER_BUILD_CONTOURS_TRACE); walkContour(x, y, i, chf, flags, verts); ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_TRACE); ctx->startTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); simplifyContour(verts, simplified, maxError, maxEdgeLen, buildFlags); removeDegenerateSegments(simplified); ctx->stopTimer(RC_TIMER_BUILD_CONTOURS_SIMPLIFY); // Store region->contour remap info. // Create contour. if (simplified.size()/4 >= 3) { if (cset.nconts >= maxContours) { // Allocate more contours. // This happens when a region has holes. const int oldMax = maxContours; maxContours *= 2; rcContour* newConts = (rcContour*)rcAlloc(sizeof(rcContour)*maxContours, RC_ALLOC_PERM); for (int j = 0; j < cset.nconts; ++j) { newConts[j] = cset.conts[j]; // Reset source pointers to prevent data deletion. cset.conts[j].verts = 0; cset.conts[j].rverts = 0; } rcFree(cset.conts); cset.conts = newConts; ctx->log(RC_LOG_WARNING, "rcBuildContours: Expanding max contours from %d to %d.", oldMax, maxContours); } rcContour* cont = &cset.conts[cset.nconts++]; cont->nverts = simplified.size()/4; cont->verts = (int*)rcAlloc(sizeof(int)*cont->nverts*4, RC_ALLOC_PERM); if (!cont->verts) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'verts' (%d).", cont->nverts); return false; } memcpy(cont->verts, &simplified[0], sizeof(int)*cont->nverts*4); if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. for (int j = 0; j < cont->nverts; ++j) { int* v = &cont->verts[j*4]; v[0] -= borderSize; v[2] -= borderSize; } } cont->nrverts = verts.size()/4; cont->rverts = (int*)rcAlloc(sizeof(int)*cont->nrverts*4, RC_ALLOC_PERM); if (!cont->rverts) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'rverts' (%d).", cont->nrverts); return false; } memcpy(cont->rverts, &verts[0], sizeof(int)*cont->nrverts*4); if (borderSize > 0) { // If the heightfield was build with bordersize, remove the offset. for (int j = 0; j < cont->nrverts; ++j) { int* v = &cont->rverts[j*4]; v[0] -= borderSize; v[2] -= borderSize; } } cont->reg = reg; cont->areaMask = areaMask; } } } } // Merge holes if needed. if (cset.nconts > 0) { // Calculate winding of all polygons. rcScopedDelete<char> winding = (char*)rcAlloc(sizeof(char)*cset.nconts, RC_ALLOC_TEMP); if (!winding) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'hole' (%d).", cset.nconts); return false; } int nholes = 0; for (int i = 0; i < cset.nconts; ++i) { rcContour& cont = cset.conts[i]; // If the contour is wound backwards, it is a hole. winding[i] = calcAreaOfPolygon2D(cont.verts, cont.nverts) < 0 ? -1 : 1; if (winding[i] < 0) nholes++; } if (nholes > 0) { // Collect outline contour and holes contours per region. // We assume that there is one outline and multiple holes. const int nregions = chf.maxRegions+1; rcScopedDelete<rcContourRegion> regions = (rcContourRegion*)rcAlloc(sizeof(rcContourRegion)*nregions, RC_ALLOC_TEMP); if (!regions) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'regions' (%d).", nregions); return false; } memset(regions, 0, sizeof(rcContourRegion)*nregions); rcScopedDelete<rcContourHole> holes = (rcContourHole*)rcAlloc(sizeof(rcContourHole)*cset.nconts, RC_ALLOC_TEMP); if (!holes) { ctx->log(RC_LOG_ERROR, "rcBuildContours: Out of memory 'holes' (%d).", cset.nconts); return false; } memset(holes, 0, sizeof(rcContourHole)*cset.nconts); for (int i = 0; i < cset.nconts; ++i) { rcContour& cont = cset.conts[i]; // Positively would contours are outlines, negative holes. if (winding[i] > 0) { if (regions[cont.reg].outline) ctx->log(RC_LOG_ERROR, "rcBuildContours: Multiple outlines for region %d.", cont.reg); regions[cont.reg].outline = &cont; } else { regions[cont.reg].nholes++; } } int index = 0; for (int i = 0; i < nregions; i++) { if (regions[i].nholes > 0) { regions[i].holes = &holes[index]; index += regions[i].nholes; regions[i].nholes = 0; } } for (int i = 0; i < cset.nconts; ++i) { rcContour& cont = cset.conts[i]; rcContourRegion& reg = regions[cont.reg]; if (winding[i] < 0) reg.holes[reg.nholes++].contour = &cont; } // Finally merge each regions holes into the outline. for (int i = 0; i < nregions; i++) { rcContourRegion& reg = regions[i]; if (!reg.nholes) continue; if (reg.outline) { mergeRegionHoles(ctx, reg); } else { // The region does not have an outline. // This can happen if the contour becaomes selfoverlapping because of // too aggressive simplification settings. ctx->log(RC_LOG_ERROR, "rcBuildContours: Bad outline for region %d, contour simplification is likely too aggressive.", i); } } } } ctx->stopTimer(RC_TIMER_BUILD_CONTOURS); return true; }