static unsigned short* boxBlur(rcCompactHeightfield& chf, int thr,
                               unsigned short* src, unsigned short* dst)
{
    const int w = chf.width;
    const int h = chf.height;
    
    thr *= 2;
    
    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];
                const unsigned short cd = src[i];
                if (cd <= thr)
                {
                    dst[i] = cd;
                    continue;
                }

                int d = (int)cd;
                for (int dir = 0; dir < 4; ++dir)
                {
                    if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                    {
                        const int ax = x + rcGetDirOffsetX(dir);
                        const int ay = y + rcGetDirOffsetY(dir);
                        const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
                        d += (int)src[ai];
                        
                        const rcCompactSpan& as = chf.spans[ai];
                        const int dir2 = (dir+1) & 0x3;
                        if (rcGetCon(as, dir2) != RC_NOT_CONNECTED)
                        {
                            const int ax2 = ax + rcGetDirOffsetX(dir2);
                            const int ay2 = ay + rcGetDirOffsetY(dir2);
                            const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);
                            d += (int)src[ai2];
                        }
                        else
                        {
                            d += cd;
                        }
                    }
                    else
                    {
                        d += cd*2;
                    }
                }
                dst[i] = (unsigned short)((d+5)/9);
            }
        }
    }
    return dst;
}
Beispiel #2
0
static bool isSolidEdge(rcCompactHeightfield& chf, unsigned short* srcReg, int x, int y, int i, int dir)
{
	const rcCompactSpan& s = chf.spans[i];
	unsigned short r = 0;
	if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
	{
		const int ax = x + rcGetDirOffsetX(dir);
		const int ay = y + rcGetDirOffsetY(dir);
		const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);
		r = srcReg[ai];
	}
	if (r == srcReg[i])
		return false;
	return true;
}
Beispiel #3
0
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 );
}
Beispiel #4
0
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 );
}
Beispiel #5
0
bool rcBuildRegionsMonotone(rcCompactHeightfield& chf,
							int borderSize, int minRegionSize, int mergeRegionSize)
{
	rcTimeVal startTime = rcGetPerformanceTimer();
	
	const int w = chf.width;
	const int h = chf.height;
	unsigned short id = 1;
	
	if (chf.regs)
	{
		delete [] chf.regs;
		chf.regs = 0;
	}
	
	rcScopedDelete<unsigned short> srcReg = new unsigned short[chf.spanCount];
	if (!srcReg)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'src' (%d).", chf.spanCount);
		return false;
	}
	memset(srcReg,0,sizeof(unsigned short)*chf.spanCount);

	rcScopedDelete<rcSweepSpan> sweeps = new rcSweepSpan[rcMax(chf.width,chf.height)];
	if (!sweeps)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcBuildRegionsMonotone: Out of memory 'sweeps' (%d).", chf.width);
		return false;
	}
	
	
	// Mark border regions.
	if (borderSize)
	{
		paintRectRegion(0, borderSize, 0, h, id|RC_BORDER_REG, chf, srcReg); id++;
		paintRectRegion(w-borderSize, w, 0, h, id|RC_BORDER_REG, chf, srcReg); id++;
		paintRectRegion(0, w, 0, borderSize, id|RC_BORDER_REG, chf, srcReg); id++;
		paintRectRegion(0, w, h-borderSize, h, id|RC_BORDER_REG, chf, srcReg); id++;
	}
	
	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;
			}
		}
	}

	rcTimeVal filterStartTime = rcGetPerformanceTimer();

	// Filter out small regions.
	chf.maxRegions = id;
	if (!filterSmallRegions(minRegionSize, mergeRegionSize, chf.maxRegions, chf, srcReg))
		return false;

	rcTimeVal filterEndTime = rcGetPerformanceTimer();
	
	// Store the result out.
	chf.regs = srcReg;
	srcReg = 0;
	
	rcTimeVal endTime = rcGetPerformanceTimer();

	if (rcGetBuildTimes())
	{
		rcGetBuildTimes()->buildRegions += rcGetDeltaTimeUsec(startTime, endTime);
		rcGetBuildTimes()->buildRegionsFilter += rcGetDeltaTimeUsec(filterStartTime, filterEndTime);
	}

	return true;
}
static void walkContour(int x, int y, int i, int dir,
                        rcCompactHeightfield& chf,
                        unsigned short* srcReg,
                        rcIntArray& cont)
{
    int startDir = dir;
    int starti = i;

    const rcCompactSpan& ss = chf.spans[i];
    unsigned short curReg = 0;
    if (rcGetCon(ss, dir) != RC_NOT_CONNECTED)
    {
        const int ax = x + rcGetDirOffsetX(dir);
        const int ay = y + rcGetDirOffsetY(dir);
        const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(ss, dir);
        curReg = srcReg[ai];
    }
    cont.push(curReg);
            
    int iter = 0;
    while (++iter < 40000)
    {
        const rcCompactSpan& s = chf.spans[i];
        
        if (isSolidEdge(chf, srcReg, x, y, i, dir))
        {
            // Choose the edge corner
            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*chf.width].index + rcGetCon(s, dir);
                r = srcReg[ai];
            }
            if (r != curReg)
            {
                curReg = r;
                cont.push(curReg);
            }
            
            dir = (dir+1) & 0x3;  // Rotate CW
        }
        else
        {
            int ni = -1;
            const int nx = x + rcGetDirOffsetX(dir);
            const int ny = y + rcGetDirOffsetY(dir);
            if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
            {
                const rcCompactCell& nc = chf.cells[nx+ny*chf.width];
                ni = (int)nc.index + rcGetCon(s, dir);
            }
            if (ni == -1)
            {
                // Should not happen.
                return;
            }
            x = nx;
            y = ny;
            i = ni;
            dir = (dir+3) & 0x3;    // Rotate CCW
        }
        
        if (starti == i && startDir == dir)
        {
            break;
        }
    }

    // Remove adjacent duplicates.
    if (cont.size() > 1)
    {
        for (int j = 0; j < cont.size(); )
        {
            int nj = (j+1) % cont.size();
            if (cont[j] == cont[nj])
            {
                for (int k = j; k < cont.size()-1; ++k)
                    cont[k] = cont[k+1];
                cont.pop();
            }
            else
                ++j;
        }
    }
}
static void calculateDistanceField(rcCompactHeightfield& chf, unsigned short* src, unsigned short& maxDist)
{
    const int w = chf.width;
    const int h = chf.height;
    
    // Init distance and points.
    for (int i = 0; i < chf.spanCount; ++i)
        src[i] = 0xffff;
    
    // 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)
            {
                const rcCompactSpan& s = chf.spans[i];
                const unsigned char area = chf.areas[i];
                
                int nc = 0;
                for (int dir = 0; dir < 4; ++dir)
                {
                    if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                    {
                        const int ax = x + rcGetDirOffsetX(dir);
                        const int ay = y + rcGetDirOffsetY(dir);
                        const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
                        if (area == chf.areas[ai])
                            nc++;
                    }
                }
                if (nc != 4)
                    src[i] = 0;
            }
        }
    }
    
            
    // 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];
                    if (src[ai]+2 < src[i])
                        src[i] = src[ai]+2;
                    
                    // (-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);
                        if (src[aai]+3 < src[i])
                            src[i] = src[aai]+3;
                    }
                }
                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];
                    if (src[ai]+2 < src[i])
                        src[i] = src[ai]+2;
                    
                    // (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);
                        if (src[aai]+3 < src[i])
                            src[i] = src[aai]+3;
                    }
                }
            }
        }
    }
    
    // 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];
                    if (src[ai]+2 < src[i])
                        src[i] = src[ai]+2;
                    
                    // (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);
                        if (src[aai]+3 < src[i])
                            src[i] = src[aai]+3;
                    }
                }
                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];
                    if (src[ai]+2 < src[i])
                        src[i] = src[ai]+2;
                    
                    // (-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);
                        if (src[aai]+3 < src[i])
                            src[i] = src[aai]+3;
                    }
                }
            }
        }
    }    
    
    maxDist = 0;
    for (int i = 0; i < chf.spanCount; ++i)
        maxDist = rcMax(src[i], maxDist);
    
}
Beispiel #8
0
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;
}
static void getHeightData(const rcCompactHeightfield& chf,
						  const unsigned short* poly, const int npoly,
						  const unsigned short* verts, const int bs,
						  rcHeightPatch& hp, rcIntArray& stack,
						  int region)
{
	// Note: Reads to the compact heightfield are offset by border size (bs)
	// since border size offset is already removed from the polymesh vertices.
	
	stack.resize(0);
	memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);
	
	bool empty = true;
	
	// Copy the height from the same region, and mark region borders
	// as seed points to fill the rest.
	for (int hy = 0; hy < hp.height; hy++)
	{
		int y = hp.ymin + hy + bs;
		for (int hx = 0; hx < hp.width; hx++)
		{
			int x = hp.xmin + hx + bs;
			const rcCompactCell& c = chf.cells[x+y*chf.width];
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				if (s.reg == region)
				{
					// Store height
					hp.data[hx + hy*hp.width] = s.y;
					empty = false;
					
					// If any of the neighbours is not in same region,
					// add the current location as flood fill start
					bool border = false;
					for (int dir = 0; dir < 4; ++dir)
					{
						if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
						{
							const int ax = x + rcGetDirOffsetX(dir);
							const int ay = y + rcGetDirOffsetY(dir);
							const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);
							const rcCompactSpan& as = chf.spans[ai];
							if (as.reg != region)
							{
								border = true;
								break;
							}
						}
					}
					if (border)
					{
						stack.push(x);
						stack.push(y);
						stack.push(i);
					}
					break;
				}
			}
		}
	}
	
	// if the polygon does not contian any points from the current region (rare, but happens)
	// then use the cells closest to the polygon vertices as seeds to fill the height field
	if (empty)
		getHeightDataSeedsFromVertices(chf, poly, npoly, verts, bs, hp, stack);
	
	static const int RETRACT_SIZE = 256;
	int head = 0;
	
	while (head*3 < stack.size())
	{
		int cx = stack[head*3+0];
		int cy = stack[head*3+1];
		int ci = stack[head*3+2];
		head++;
		if (head >= RETRACT_SIZE)
		{
			head = 0;
			if (stack.size() > RETRACT_SIZE*3)
				memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3));
			stack.resize(stack.size()-RETRACT_SIZE*3);
		}
		
		const rcCompactSpan& cs = chf.spans[ci];
		for (int dir = 0; dir < 4; ++dir)
		{
			if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue;
			
			const int ax = cx + rcGetDirOffsetX(dir);
			const int ay = cy + rcGetDirOffsetY(dir);
			const int hx = ax - hp.xmin - bs;
			const int hy = ay - hp.ymin - bs;
			
			if (hx < 0 || hx >= hp.width || hy < 0 || hy >= hp.height)
				continue;
			
			if (hp.data[hx + hy*hp.width] != RC_UNSET_HEIGHT)
				continue;
			
			const int ai = (int)chf.cells[ax + ay*chf.width].index + rcGetCon(cs, dir);
			const rcCompactSpan& as = chf.spans[ai];
			
			hp.data[hx + hy*hp.width] = as.y;
			
			stack.push(ax);
			stack.push(ay);
			stack.push(ai);
		}
	}
}
Beispiel #10
0
/// @par
/// 
/// See the #rcConfig documentation for more information on the configuration parameters.
/// 
/// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig
bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf,
							  const int borderSize, const int walkableHeight,
							  rcHeightfieldLayerSet& lset)
{
	rcAssert(ctx);
	
	rcScopedTimer timer(ctx, RC_TIMER_BUILD_LAYERS);
	
	const int w = chf.width;
	const int h = chf.height;
	
	rcScopedDelete<unsigned char> srcReg((unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP));
	if (!srcReg)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' (%d).", chf.spanCount);
		return false;
	}
	memset(srcReg,0xff,sizeof(unsigned char)*chf.spanCount);
	
	const int nsweeps = chf.width;
	rcScopedDelete<rcLayerSweepSpan> sweeps((rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP));
	if (!sweeps)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps);
		return false;
	}
	
	
	// Partition walkable area into monotone regions.
	int prevCount[256];
	unsigned char regId = 0;

	for (int y = borderSize; y < h-borderSize; ++y)
	{
		memset(prevCount,0,sizeof(int)*regId);
		unsigned char sweepId = 0;
		
		for (int x = borderSize; x < w-borderSize; ++x)
		{
			const rcCompactCell& c = chf.cells[x+y*w];
			
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				if (chf.areas[i] == RC_NULL_AREA) continue;

				unsigned char sid = 0xff;

				// -x
				if (rcGetCon(s, 0) != RC_NOT_CONNECTED)
				{
					const int ax = x + rcGetDirOffsetX(0);
					const int ay = y + rcGetDirOffsetY(0);
					const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0);
					if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
						sid = srcReg[ai];
				}
				
				if (sid == 0xff)
				{
					sid = sweepId++;
					sweeps[sid].nei = 0xff;
					sweeps[sid].ns = 0;
				}
				
				// -y
				if (rcGetCon(s,3) != RC_NOT_CONNECTED)
				{
					const int ax = x + rcGetDirOffsetX(3);
					const int ay = y + rcGetDirOffsetY(3);
					const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3);
					const unsigned char nr = srcReg[ai];
					if (nr != 0xff)
					{
						// Set neighbour when first valid neighbour is encoutered.
						if (sweeps[sid].ns == 0)
							sweeps[sid].nei = nr;
						
						if (sweeps[sid].nei == nr)
						{
							// Update existing neighbour
							sweeps[sid].ns++;
							prevCount[nr]++;
						}
						else
						{
							// This is hit if there is nore than one neighbour.
							// Invalidate the neighbour.
							sweeps[sid].nei = 0xff;
						}
					}
				}
				
				srcReg[i] = sid;
			}
		}
		
		// Create unique ID.
		for (int i = 0; i < sweepId; ++i)
		{
			// If the neighbour is set and there is only one continuous connection to it,
			// the sweep will be merged with the previous one, else new region is created.
			if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == (int)sweeps[i].ns)
			{
				sweeps[i].id = sweeps[i].nei;
			}
			else
			{
				if (regId == 255)
				{
					ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow.");
					return false;
				}
				sweeps[i].id = regId++;
			}
		}
		
		// Remap local sweep ids to region ids.
		for (int x = borderSize; x < w-borderSize; ++x)
		{
			const rcCompactCell& c = chf.cells[x+y*w];
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				if (srcReg[i] != 0xff)
					srcReg[i] = sweeps[srcReg[i]].id;
			}
		}
	}

	// Allocate and init layer regions.
	const int nregs = (int)regId;
	rcScopedDelete<rcLayerRegion> regs((rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP));
	if (!regs)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs);
		return false;
	}
	memset(regs, 0, sizeof(rcLayerRegion)*nregs);
	for (int i = 0; i < nregs; ++i)
	{
		regs[i].layerId = 0xff;
		regs[i].ymin = 0xffff;
		regs[i].ymax = 0;
	}
	
	// Find region neighbours and overlapping regions.
	for (int y = 0; y < h; ++y)
	{
		for (int x = 0; x < w; ++x)
		{
			const rcCompactCell& c = chf.cells[x+y*w];
			
			unsigned char lregs[RC_MAX_LAYERS];
			int nlregs = 0;
			
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				const unsigned char ri = srcReg[i];
				if (ri == 0xff) continue;
				
				regs[ri].ymin = rcMin(regs[ri].ymin, s.y);
				regs[ri].ymax = rcMax(regs[ri].ymax, s.y);
				
				// Collect all region layers.
				if (nlregs < RC_MAX_LAYERS)
					lregs[nlregs++] = ri;
				
				// Update neighbours
				for (int dir = 0; dir < 4; ++dir)
				{
					if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
					{
						const int ax = x + rcGetDirOffsetX(dir);
						const int ay = y + rcGetDirOffsetY(dir);
						const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
						const unsigned char rai = srcReg[ai];
						if (rai != 0xff && rai != ri)
						{
							// Don't check return value -- if we cannot add the neighbor
							// it will just cause a few more regions to be created, which
							// is fine.
							addUnique(regs[ri].neis, regs[ri].nneis, RC_MAX_NEIS, rai);
						}
					}
				}
				
			}
			
			// Update overlapping regions.
			for (int i = 0; i < nlregs-1; ++i)
			{
				for (int j = i+1; j < nlregs; ++j)
				{
					if (lregs[i] != lregs[j])
					{
						rcLayerRegion& ri = regs[lregs[i]];
						rcLayerRegion& rj = regs[lregs[j]];

						if (!addUnique(ri.layers, ri.nlayers, RC_MAX_LAYERS, lregs[j]) ||
							!addUnique(rj.layers, rj.nlayers, RC_MAX_LAYERS, lregs[i]))
						{
							ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS.");
							return false;
						}
					}
				}
			}
			
		}
	}
	
	// Create 2D layers from regions.
	unsigned char layerId = 0;
	
	static const int MAX_STACK = 64;
	unsigned char stack[MAX_STACK];
	int nstack = 0;
	
	for (int i = 0; i < nregs; ++i)
	{
		rcLayerRegion& root = regs[i];
		// Skip already visited.
		if (root.layerId != 0xff)
			continue;

		// Start search.
		root.layerId = layerId;
		root.base = 1;
		
		nstack = 0;
		stack[nstack++] = (unsigned char)i;
		
		while (nstack)
		{
			// Pop front
			rcLayerRegion& reg = regs[stack[0]];
			nstack--;
			for (int j = 0; j < nstack; ++j)
				stack[j] = stack[j+1];
			
			const int nneis = (int)reg.nneis;
			for (int j = 0; j < nneis; ++j)
			{
				const unsigned char nei = reg.neis[j];
				rcLayerRegion& regn = regs[nei];
				// Skip already visited.
				if (regn.layerId != 0xff)
					continue;
				// Skip if the neighbour is overlapping root region.
				if (contains(root.layers, root.nlayers, nei))
					continue;
				// Skip if the height range would become too large.
				const int ymin = rcMin(root.ymin, regn.ymin);
				const int ymax = rcMax(root.ymax, regn.ymax);
				if ((ymax - ymin) >= 255)
					 continue;

				if (nstack < MAX_STACK)
				{
					// Deepen
					stack[nstack++] = (unsigned char)nei;
					
					// Mark layer id
					regn.layerId = layerId;
					// Merge current layers to root.
					for (int k = 0; k < regn.nlayers; ++k)
					{
						if (!addUnique(root.layers, root.nlayers, RC_MAX_LAYERS, regn.layers[k]))
						{
							ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS.");
							return false;
						}
					}
					root.ymin = rcMin(root.ymin, regn.ymin);
					root.ymax = rcMax(root.ymax, regn.ymax);
				}
			}
		}
		
		layerId++;
	}
	
	// Merge non-overlapping regions that are close in height.
	const unsigned short mergeHeight = (unsigned short)walkableHeight * 4;
	
	for (int i = 0; i < nregs; ++i)
	{
		rcLayerRegion& ri = regs[i];
		if (!ri.base) continue;
		
		unsigned char newId = ri.layerId;
		
		for (;;)
		{
			unsigned char oldId = 0xff;
			
			for (int j = 0; j < nregs; ++j)
			{
				if (i == j) continue;
				rcLayerRegion& rj = regs[j];
				if (!rj.base) continue;
				
				// Skip if the regions are not close to each other.
				if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight))
					continue;
				// Skip if the height range would become too large.
				const int ymin = rcMin(ri.ymin, rj.ymin);
				const int ymax = rcMax(ri.ymax, rj.ymax);
				if ((ymax - ymin) >= 255)
				  continue;
						  
				// Make sure that there is no overlap when merging 'ri' and 'rj'.
				bool overlap = false;
				// Iterate over all regions which have the same layerId as 'rj'
				for (int k = 0; k < nregs; ++k)
				{
					if (regs[k].layerId != rj.layerId)
						continue;
					// Check if region 'k' is overlapping region 'ri'
					// Index to 'regs' is the same as region id.
					if (contains(ri.layers,ri.nlayers, (unsigned char)k))
					{
						overlap = true;
						break;
					}
				}
				// Cannot merge of regions overlap.
				if (overlap)
					continue;
				
				// Can merge i and j.
				oldId = rj.layerId;
				break;
			}
			
			// Could not find anything to merge with, stop.
			if (oldId == 0xff)
				break;
			
			// Merge
			for (int j = 0; j < nregs; ++j)
			{
				rcLayerRegion& rj = regs[j];
				if (rj.layerId == oldId)
				{
					rj.base = 0;
					// Remap layerIds.
					rj.layerId = newId;
					// Add overlaid layers from 'rj' to 'ri'.
					for (int k = 0; k < rj.nlayers; ++k)
					{
						if (!addUnique(ri.layers, ri.nlayers, RC_MAX_LAYERS, rj.layers[k]))
						{
							ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: layer overflow (too many overlapping walkable platforms). Try increasing RC_MAX_LAYERS.");
							return false;
						}
					}

					// Update height bounds.
					ri.ymin = rcMin(ri.ymin, rj.ymin);
					ri.ymax = rcMax(ri.ymax, rj.ymax);
				}
			}
		}
	}
	
	// Compact layerIds
	unsigned char remap[256];
	memset(remap, 0, 256);

	// Find number of unique layers.
	layerId = 0;
	for (int i = 0; i < nregs; ++i)
		remap[regs[i].layerId] = 1;
	for (int i = 0; i < 256; ++i)
	{
		if (remap[i])
			remap[i] = layerId++;
		else
			remap[i] = 0xff;
	}
	// Remap ids.
	for (int i = 0; i < nregs; ++i)
		regs[i].layerId = remap[regs[i].layerId];
	
	// No layers, return empty.
	if (layerId == 0)
		return true;
	
	// Create layers.
	rcAssert(lset.layers == 0);
	
	const int lw = w - borderSize*2;
	const int lh = h - borderSize*2;

	// Build contracted bbox for layers.
	float bmin[3], bmax[3];
	rcVcopy(bmin, chf.bmin);
	rcVcopy(bmax, chf.bmax);
	bmin[0] += borderSize*chf.cs;
	bmin[2] += borderSize*chf.cs;
	bmax[0] -= borderSize*chf.cs;
	bmax[2] -= borderSize*chf.cs;
	
	lset.nlayers = (int)layerId;
	
	lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM);
	if (!lset.layers)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers);
		return false;
	}
	memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers);

	
	// Store layers.
	for (int i = 0; i < lset.nlayers; ++i)
	{
		unsigned char curId = (unsigned char)i;

		rcHeightfieldLayer* layer = &lset.layers[i];

		const int gridSize = sizeof(unsigned char)*lw*lh;

		layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
		if (!layer->heights)
		{
			ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize);
			return false;
		}
		memset(layer->heights, 0xff, gridSize);

		layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
		if (!layer->areas)
		{
			ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize);
			return false;
		}
		memset(layer->areas, 0, gridSize);

		layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
		if (!layer->cons)
		{
			ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize);
			return false;
		}
		memset(layer->cons, 0, gridSize);
		
		// Find layer height bounds.
		int hmin = 0, hmax = 0;
		for (int j = 0; j < nregs; ++j)
		{
			if (regs[j].base && regs[j].layerId == curId)
			{
				hmin = (int)regs[j].ymin;
				hmax = (int)regs[j].ymax;
			}
		}

		layer->width = lw;
		layer->height = lh;
		layer->cs = chf.cs;
		layer->ch = chf.ch;
		
		// Adjust the bbox to fit the heightfield.
		rcVcopy(layer->bmin, bmin);
		rcVcopy(layer->bmax, bmax);
		layer->bmin[1] = bmin[1] + hmin*chf.ch;
		layer->bmax[1] = bmin[1] + hmax*chf.ch;
		layer->hmin = hmin;
		layer->hmax = hmax;

		// Update usable data region.
		layer->minx = layer->width;
		layer->maxx = 0;
		layer->miny = layer->height;
		layer->maxy = 0;
		
		// Copy height and area from compact heightfield. 
		for (int y = 0; y < lh; ++y)
		{
			for (int x = 0; x < lw; ++x)
			{
				const int cx = borderSize+x;
				const int cy = borderSize+y;
				const rcCompactCell& c = chf.cells[cx+cy*w];
				for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j)
				{
					const rcCompactSpan& s = chf.spans[j];
					// Skip unassigned regions.
					if (srcReg[j] == 0xff)
						continue;
					// Skip of does nto belong to current layer.
					unsigned char lid = regs[srcReg[j]].layerId;
					if (lid != curId)
						continue;
					
					// Update data bounds.
					layer->minx = rcMin(layer->minx, x);
					layer->maxx = rcMax(layer->maxx, x);
					layer->miny = rcMin(layer->miny, y);
					layer->maxy = rcMax(layer->maxy, y);
					
					// Store height and area type.
					const int idx = x+y*lw;
					layer->heights[idx] = (unsigned char)(s.y - hmin);
					layer->areas[idx] = chf.areas[j];
					
					// Check connection.
					unsigned char portal = 0;
					unsigned char con = 0;
					for (int dir = 0; dir < 4; ++dir)
					{
						if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
						{
							const int ax = cx + rcGetDirOffsetX(dir);
							const int ay = cy + rcGetDirOffsetY(dir);
							const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
							unsigned char alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff;
							// Portal mask
							if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
							{
								portal |= (unsigned char)(1<<dir);
								// Update height so that it matches on both sides of the portal.
								const rcCompactSpan& as = chf.spans[ai];
								if (as.y > hmin)
									layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin));
							}
							// Valid connection mask
							if (chf.areas[ai] != RC_NULL_AREA && lid == alid)
							{
								const int nx = ax - borderSize;
								const int ny = ay - borderSize;
								if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
									con |= (unsigned char)(1<<dir);
							}
						}
					}
					
					layer->cons[idx] = (portal << 4) | con;
				}
			}
		}
		
		if (layer->minx > layer->maxx)
			layer->minx = layer->maxx = 0;
		if (layer->miny > layer->maxy)
			layer->miny = layer->maxy = 0;
	}
	
	return true;
}
static int getCornerHeight(int x, int y, int i, int dir,
						   const rcCompactHeightfield& chf,
						   bool& isBorderVertex)
{
	const rcCompactSpan& s = chf.spans[i];
	int ch = (int)s.minY;
	int dirp = (dir+1) & 0x3;
	
	struct CornerId
	{
		uint64_t		areaMask;
		unsigned short	region;

		bool No0() const
		{
			return areaMask != 0 && region != 0;
		}

		CornerId( unsigned short reg = 0, uint64_t area = 0 )
			: areaMask( area )
			, region( reg )
		{
		}
	};

	CornerId regs[ 4 ];
	
	// Combine region and area codes in order to prevent
	// border vertices which are in between two areas to be removed. 
	regs[ 0 ] = CornerId( chf.spans[ i ].regionID, chf.areaMasks[ i ] );
	
	if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
	{
		const int ax = x + rcGetDirOffsetX(dir);
		const int ay = y + rcGetDirOffsetY(dir);
		const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);
		const rcCompactSpan& as = chf.spans[ai];
		ch = rcMax(ch, (int)as.minY);
		regs[ 1 ] = CornerId( chf.spans[ ai ].regionID, chf.areaMasks[ ai ] );
		if (rcGetCon(as, dirp) != RC_NOT_CONNECTED)
		{
			const int ax2 = ax + rcGetDirOffsetX(dirp);
			const int ay2 = ay + rcGetDirOffsetY(dirp);
			const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dirp);
			const rcCompactSpan& as2 = chf.spans[ai2];
			ch = rcMax(ch, (int)as2.minY);
			regs[ 2 ] = CornerId( chf.spans[ ai2 ].regionID, chf.areaMasks[ ai2 ] );
		}
	}
	if (rcGetCon(s, dirp) != RC_NOT_CONNECTED)
	{
		const int ax = x + rcGetDirOffsetX(dirp);
		const int ay = y + rcGetDirOffsetY(dirp);
		const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dirp);
		const rcCompactSpan& as = chf.spans[ai];
		ch = rcMax(ch, (int)as.minY);
		regs[ 3 ] = CornerId( chf.spans[ ai ].regionID, chf.areaMasks[ ai ] );
		if (rcGetCon(as, dir) != RC_NOT_CONNECTED)
		{
			const int ax2 = ax + rcGetDirOffsetX(dir);
			const int ay2 = ay + rcGetDirOffsetY(dir);
			const int ai2 = (int)chf.cells[ax2+ay2*chf.width].index + rcGetCon(as, dir);
			const rcCompactSpan& as2 = chf.spans[ai2];
			ch = rcMax(ch, (int)as2.minY);
			regs[ 2 ] = CornerId( chf.spans[ ai2 ].regionID, chf.areaMasks[ ai2 ] );
		}
	}

	// Check if the vertex is special edge vertex, these vertices will be removed later.
	for (int j = 0; j < 4; ++j)
	{
		const int a = j;
		const int b = (j+1) & 0x3;
		const int c = (j+2) & 0x3;
		const int d = (j+3) & 0x3;
		
		// The vertex is a border vertex there are two same exterior cells in a row,
		// followed by two interior cells and none of the regions are out of bounds.
		const bool twoSameExts = ( regs[ a ].region & regs[ b ].region & RC_BORDER_REG ) != 0 && regs[ a ].region == regs[ b ].region;
		const bool twoInts = ( ( regs[ c ].region | regs[ d ].region ) & RC_BORDER_REG ) == 0;
		const bool intsSameArea = ( regs[ c ].areaMask ) == ( regs[ d ].areaMask );
		const bool noZeros = regs[ a ].No0() && regs[ b ].No0() && regs[ c ].No0() && regs[ d ].No0();
		if (twoSameExts && twoInts && intsSameArea && noZeros)
		{
			isBorderVertex = true;
			break;
		}
	}
	
	return ch;
}
static void walkContour(int x, int y, int i,
						rcCompactHeightfield& chf,
						unsigned char* flags, rcIntArray& points)
{
	// Choose the first non-connected edge
	unsigned char dir = 0;
	while ((flags[i] & (1 << dir)) == 0)
		dir++;
	
	unsigned char startDir = dir;
	int starti = i;
	
	const navAreaMask area = chf.areaMasks[ i ];
	
	int iter = 0;
	while (++iter < 40000)
	{
		if (flags[i] & (1 << dir))
		{
			// Choose the edge corner
			bool isBorderVertex = false;
			bool isAreaBorder = false;
			int px = x;
			int py = getCornerHeight(x, y, i, dir, chf, isBorderVertex);
			int pz = y;
			switch(dir)
			{
				case 0: pz++; break;
				case 1: px++; pz++; break;
				case 2: px++; break;
			}
			int r = 0;
			const rcCompactSpan& s = chf.spans[i];
			if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
			{
				const int ax = x + rcGetDirOffsetX(dir);
				const int ay = y + rcGetDirOffsetY(dir);
				const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(s, dir);
				r = (int)chf.spans[ai].regionID;
				if (area != chf.areaMasks[ai])
					isAreaBorder = true;
			}
			if (isBorderVertex)
				r |= RC_BORDER_VERTEX;
			if (isAreaBorder)
				r |= RC_AREA_BORDER;
			points.push(px);
			points.push(py);
			points.push(pz);
			points.push(r);
			
			flags[i] &= ~(1 << dir); // Remove visited edges
			dir = (dir+1) & 0x3;  // Rotate CW
		}
		else
		{
			int ni = -1;
			const int nx = x + rcGetDirOffsetX(dir);
			const int ny = y + rcGetDirOffsetY(dir);
			const rcCompactSpan& s = chf.spans[i];
			if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
			{
				const rcCompactCell& nc = chf.cells[nx+ny*chf.width];
				ni = (int)nc.index + rcGetCon(s, dir);
			}
			if (ni == -1)
			{
				// Should not happen.
				return;
			}
			x = nx;
			y = ny;
			i = ni;
			dir = (dir+3) & 0x3;	// Rotate CCW
		}
		
		if (starti == i && startDir == dir)
		{
			break;
		}
	}
}
static bool floodRegion(int x, int y, int i,
						unsigned short level, unsigned short minLevel, unsigned short r,
						rcCompactHeightfield& chf,
						unsigned short* src,
						rcIntArray& stack)
{
	const int w = chf.width;
	
	// Flood fill mark region.
	stack.resize(0);
	stack.push((int)x);
	stack.push((int)y);
	stack.push((int)i);
	src[i*2] = r;
	src[i*2+1] = 0;
	
	unsigned short lev = level >= minLevel+2 ? level-2 : minLevel;
	int count = 0;
	
	while (stack.size() > 0)
	{
		int ci = stack.pop();
		int cy = stack.pop();
		int cx = stack.pop();
		
		const rcCompactSpan& cs = chf.spans[ci];
		
		// Check if any of the neighbours already have a valid region set.
		unsigned short ar = 0;
		for (int dir = 0; dir < 4; ++dir)
		{
			// 8 connected
			if (rcGetCon(cs, dir) != 0xf)
			{
				const int ax = cx + rcGetDirOffsetX(dir);
				const int ay = cy + rcGetDirOffsetY(dir);
				const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);
				unsigned short nr = src[ai*2];
				if (nr != 0 && nr != r)
					ar = nr;
				
				const rcCompactSpan& as = chf.spans[ai];
				
				const int dir2 = (dir+1) & 0x3;
				if (rcGetCon(as, dir2) != 0xf)
				{
					const int ax2 = ax + rcGetDirOffsetX(dir2);
					const int ay2 = ay + rcGetDirOffsetY(dir2);
					const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);
					
					unsigned short nr = src[ai2*2];
					if (nr != 0 && nr != r)
						ar = nr;
				}				
			}
		}
		if (ar != 0)
		{
			src[ci*2] = 0;
			continue;
		}
		count++;
		
		// Expand neighbours.
		for (int dir = 0; dir < 4; ++dir)
		{
			if (rcGetCon(cs, dir) != 0xf)
			{
				const int ax = cx + rcGetDirOffsetX(dir);
				const int ay = cy + rcGetDirOffsetY(dir);
				const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);
				if (chf.spans[ai].dist >= lev)
				{
					if (src[ai*2] == 0)
					{
						src[ai*2] = r;
						src[ai*2+1] = 0;
						stack.push(ax);
						stack.push(ay);
						stack.push(ai);
					}
				}
			}
		}
	}
	
	return count > 0;
}
bool rcMarkReachableSpans(const int walkableHeight,
						  const int walkableClimb,
						  rcHeightfield& solid)
{
	const int w = solid.width;
	const int h = solid.height;
	const int MAX_HEIGHT = 0xffff;
	
	rcTimeVal startTime = rcGetPerformanceTimer();
	
	// Build navigable space.
	const int MAX_SEEDS = w*h;
	rcReachableSeed* stack = new rcReachableSeed[MAX_SEEDS];
	if (!stack)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "rcMarkReachableSpans: Out of memory 'stack' (%d).", MAX_SEEDS);
		return false;
	}
	int stackSize = 0;
	
	for (int y = 0; y < h; ++y)
	{
		for (int x = 0; x < w; ++x)
		{
			rcSpan* topSpan = solid.spans[x + y*w];
			if (!topSpan)
				continue;
			while (topSpan->next)
				topSpan = topSpan->next;
			
			// If the span is not walkable, skip it.
			if ((topSpan->flags & RC_WALKABLE) == 0)
				continue;
			// If the span has been visited already, skip it.
			if (topSpan->flags & RC_REACHABLE)
				continue;
			
			// Start flood fill.
			topSpan->flags |= RC_REACHABLE;
			stackSize = 0;
			stack[stackSize].set(x, y, topSpan);
			stackSize++;
			
			while (stackSize)
			{
				// Pop a seed from the stack.
				stackSize--;
				rcReachableSeed cur = stack[stackSize];
				
				const int bot = (int)cur.s->smax;
				const int top = (int)cur.s->next ? (int)cur.s->next->smin : MAX_HEIGHT;
				
				// Visit neighbours in all 4 directions.
				for (int dir = 0; dir < 4; ++dir)
				{
					int dx = (int)cur.x + rcGetDirOffsetX(dir);
					int dy = (int)cur.y + rcGetDirOffsetY(dir);
					// Skip neighbour which are out of bounds.
					if (dx < 0 || dy < 0 || dx >= w || dy >= h)
						continue;
					for (rcSpan* ns = solid.spans[dx + dy*w]; ns; ns = ns->next)
					{
						// Skip neighbour if it is not walkable.
						if ((ns->flags & RC_WALKABLE) == 0)
							continue;
						// Skip the neighbour if it has been visited already.
						if (ns->flags & RC_REACHABLE)
							continue;
						
						const int nbot = (int)ns->smax;
						const int ntop = (int)ns->next ? (int)ns->next->smin : MAX_HEIGHT;
						// Skip neightbour if the gap between the spans is too small.
						if (rcMin(top,ntop) - rcMax(bot,nbot) < walkableHeight)
							continue;
						// Skip neightbour if the climb height to the neighbour is too high.
						if (rcAbs(nbot - bot) >= walkableClimb)
							continue;
						
						// This neighbour has not been visited yet.
						// Mark it as reachable and add it to the seed stack.
						ns->flags |= RC_REACHABLE;
						if (stackSize < MAX_SEEDS)
						{
							stack[stackSize].set(dx, dy, ns);
							stackSize++;
						}
					}
				}
			}
		}
	}
	
	delete [] stack;	
	
	rcTimeVal endTime = rcGetPerformanceTimer();
	
//	if (rcGetLog())
//		rcGetLog()->log(RC_LOG_PROGRESS, "Mark reachable: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
	if (rcGetBuildTimes())
		rcGetBuildTimes()->filterMarkReachable += rcGetDeltaTimeUsec(startTime, endTime);
	
	return true;
}
Beispiel #15
0
bool rcErodeArea(unsigned char areaId, int radius, rcCompactHeightfield& chf)
{
        const int w = chf.width;
        const int h = chf.height;
        
        rcTimeVal startTime = rcGetPerformanceTimer();
        
        unsigned char* dist = new unsigned char[chf.spanCount];
        if (!dist)
                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) != 0xf)
                                                {
                                                        const int ax = x + rcGetDirOffsetX(dir);
                                                        const int ay = y + rcGetDirOffsetY(dir);
                                                        const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
                                                        if (chf.areas[ai] == areaId)
                                                                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) != 0xf)
                                {
                                        // (-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) != 0xf)
                                        {
                                                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) != 0xf)
                                {
                                        // (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) != 0xf)
                                        {
                                                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) != 0xf)
                                {
                                        // (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) != 0xf)
                                        {
                                                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) != 0xf)
                                {
                                        // (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) != 0xf)
                                        {
                                                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] = 0;
        
        delete [] dist;
        
        rcTimeVal endTime = rcGetPerformanceTimer();
        
        if (rcGetBuildTimes())
        {
                rcGetBuildTimes()->erodeArea += rcGetDeltaTimeUsec(startTime, endTime);
        }
        
        return true;
}
Beispiel #16
0
void rcFilterLedgeSpans(const int walkableHeight,
						const int walkableClimb,
						rcHeightfield& solid)
{
	rcTimeVal startTime = rcGetPerformanceTimer();

	const int w = solid.width;
	const int h = solid.height;
	const int MAX_HEIGHT = 0xffff;
	
	// Mark border spans.
	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)
			{
				// Skip non walkable spans.
				if ((s->flags & RC_WALKABLE) == 0)
					continue;
				
				const int bot = (int)(s->smax);
				const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT;
				
				// Find neighbours minimum height.
				int minh = MAX_HEIGHT;

				// Min and max height of accessible neighbours.
				int asmin = s->smax;
				int asmax = s->smax;

				for (int dir = 0; dir < 4; ++dir)
				{
					int dx = x + rcGetDirOffsetX(dir);
					int dy = y + rcGetDirOffsetY(dir);
					// Skip neighbours which are out of bounds.
					if (dx < 0 || dy < 0 || dx >= w || dy >= h)
					{
						minh = rcMin(minh, -walkableClimb - bot);
						continue;
					}

					// From minus infinity to the first span.
					rcSpan* ns = solid.spans[dx + dy*w];
					int nbot = -walkableClimb;
					int ntop = ns ? (int)ns->smin : MAX_HEIGHT;
					// Skip neightbour if the gap between the spans is too small.
					if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight)
						minh = rcMin(minh, nbot - bot);
					
					// Rest of the spans.
					for (ns = solid.spans[dx + dy*w]; ns; ns = ns->next)
					{
						nbot = (int)ns->smax;
						ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT;
						// Skip neightbour if the gap between the spans is too small.
						if (rcMin(top,ntop) - rcMax(bot,nbot) > walkableHeight)
						{
							minh = rcMin(minh, nbot - bot);
						
							// Find min/max accessible neighbour height. 
							if (rcAbs(nbot - bot) <= walkableClimb)
							{
								if (nbot < asmin) asmin = nbot;
								if (nbot > asmax) asmax = nbot;
							}
							
						}
					}
				}
				
				// The current span is close to a ledge if the drop to any
				// neighbour span is less than the walkableClimb.
				if (minh < -walkableClimb)
					s->flags |= RC_LEDGE;
					
				// If the difference between all neighbours is too large,
				// we are at steep slope, mark the span as ledge.
				if ((asmax - asmin) > walkableClimb)
				{
					s->flags |= RC_LEDGE;
				}
			}
		}
	}
	
	rcTimeVal endTime = rcGetPerformanceTimer();
//	if (rcGetLog())
//		rcGetLog()->log(RC_LOG_PROGRESS, "Filter border: %.3f ms", rcGetDeltaTimeUsec(startTime, endTime)/1000.0f);
	if (rcGetBuildTimes())
		rcGetBuildTimes()->filterBorder += rcGetDeltaTimeUsec(startTime, endTime);
}	
Beispiel #17
0
void rcFilterLedgeSpans(rcContext* ctx, const int walkableHeight, const int walkableClimb,
	rcHeightfield& solid)
{
	rcAssert(ctx);

	ctx->startTimer(RC_TIMER_FILTER_BORDER);

	const int w = solid.width;
	const int h = solid.height;
	const int MAX_HEIGHT = 0xffff;

	// Mark border spans.
	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)
			{
				// Skip non walkable spans.
				if (s->area == RC_NULL_AREA)
					continue;

				const int bot = (int)(s->smax);
				const int top = s->next ? (int)(s->next->smin) : MAX_HEIGHT;

				// Find neighbours minimum height.
				int minh = MAX_HEIGHT;

				// Min and max height of accessible neighbours.
				int asmin = s->smax;
				int asmax = s->smax;

				for (int dir = 0; dir < 4; ++dir)
				{
					int dx = x + rcGetDirOffsetX(dir);
					int dy = y + rcGetDirOffsetY(dir);
					// Skip neighbours which are out of bounds.
					if (dx < 0 || dy < 0 || dx >= w || dy >= h)
					{
						minh = rcMin(minh, -walkableClimb - bot);
						continue;
					}

					// From minus infinity to the first span.
					rcSpan* ns = solid.spans[dx + dy*w];
					int nbot = -walkableClimb;
					int ntop = ns ? (int)ns->smin : MAX_HEIGHT;
					// Skip neightbour if the gap between the spans is too small.
					if (rcMin(top, ntop) - rcMax(bot, nbot) > walkableHeight)
						minh = rcMin(minh, nbot - bot);

					// Rest of the spans.
					for (ns = solid.spans[dx + dy*w]; ns; ns = ns->next)
					{
						nbot = (int)ns->smax;
						ntop = ns->next ? (int)ns->next->smin : MAX_HEIGHT;
						// Skip neightbour if the gap between the spans is too small.
						if (rcMin(top, ntop) - rcMax(bot, nbot) > walkableHeight)
						{
							minh = rcMin(minh, nbot - bot);

							// Find min/max accessible neighbour height.
							if (rcAbs(nbot - bot) <= walkableClimb)
							{
								if (nbot < asmin) asmin = nbot;
								if (nbot > asmax) asmax = nbot;
							}
						}
					}
				}

				// The current span is close to a ledge if the drop to any
				// neighbour span is less than the walkableClimb.
				if (minh < -walkableClimb)
					s->area = RC_NULL_AREA;

				// If the difference between all neighbours is too large,
				// we are at steep slope, mark the span as ledge.
				if ((asmax - asmin) > walkableClimb)
				{
					s->area = RC_NULL_AREA;
				}
			}
		}
	}

	ctx->stopTimer(RC_TIMER_FILTER_BORDER);
}
/// @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;
}
static void getHeightDataSeedsFromVertices(const rcCompactHeightfield& chf,
										   const unsigned short* poly, const int npoly,
										   const unsigned short* verts, const int bs,
										   rcHeightPatch& hp, rcIntArray& stack)
{
	// Floodfill the heightfield to get 2D height data,
	// starting at vertex locations as seeds.
	
	// Note: Reads to the compact heightfield are offset by border size (bs)
	// since border size offset is already removed from the polymesh vertices.
	
	memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height);
	
	stack.resize(0);
	
	static const int offset[9*2] =
	{
		0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0,
	};
	
	// Use poly vertices as seed points for the flood fill.
	for (int j = 0; j < npoly; ++j)
	{
		int cx = 0, cz = 0, ci =-1;
		int dmin = RC_UNSET_HEIGHT;
		for (int k = 0; k < 9; ++k)
		{
			const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0];
			const int ay = (int)verts[poly[j]*3+1];
			const int az = (int)verts[poly[j]*3+2] + offset[k*2+1];
			if (ax < hp.xmin || ax >= hp.xmin+hp.width ||
				az < hp.ymin || az >= hp.ymin+hp.height)
				continue;
			
			const rcCompactCell& c = chf.cells[(ax+bs)+(az+bs)*chf.width];
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				int d = rcAbs(ay - (int)s.y);
				if (d < dmin)
				{
					cx = ax;
					cz = az;
					ci = i;
					dmin = d;
				}
			}
		}
		if (ci != -1)
		{
			stack.push(cx);
			stack.push(cz);
			stack.push(ci);
		}
	}
	
	// Find center of the polygon using flood fill.
	int pcx = 0, pcz = 0;
	for (int j = 0; j < npoly; ++j)
	{
		pcx += (int)verts[poly[j]*3+0];
		pcz += (int)verts[poly[j]*3+2];
	}
	pcx /= npoly;
	pcz /= npoly;
	
	for (int i = 0; i < stack.size(); i += 3)
	{
		int cx = stack[i+0];
		int cy = stack[i+1];
		int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width;
		hp.data[idx] = 1;
	}
	
	while (stack.size() > 0)
	{
		int ci = stack.pop();
		int cy = stack.pop();
		int cx = stack.pop();
		
		// Check if close to center of the polygon.
		if (rcAbs(cx-pcx) <= 1 && rcAbs(cy-pcz) <= 1)
		{
			stack.resize(0);
			stack.push(cx);
			stack.push(cy);
			stack.push(ci);
			break;
		}
		
		const rcCompactSpan& cs = chf.spans[ci];
		
		for (int dir = 0; dir < 4; ++dir)
		{
			if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue;
			
			const int ax = cx + rcGetDirOffsetX(dir);
			const int ay = cy + rcGetDirOffsetY(dir);
			
			if (ax < hp.xmin || ax >= (hp.xmin+hp.width) ||
				ay < hp.ymin || ay >= (hp.ymin+hp.height))
				continue;
			
			if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0)
				continue;
			
			const int ai = (int)chf.cells[(ax+bs)+(ay+bs)*chf.width].index + rcGetCon(cs, dir);
			
			int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width;
			hp.data[idx] = 1;
			
			stack.push(ax);
			stack.push(ay);
			stack.push(ai);
		}
	}
	
	memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);
	
	// Mark start locations.
	for (int i = 0; i < stack.size(); i += 3)
	{
		int cx = stack[i+0];
		int cy = stack[i+1];
		int ci = stack[i+2];
		int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width;
		const rcCompactSpan& cs = chf.spans[ci];
		hp.data[idx] = cs.y;
		
		// getHeightData seeds are given in coordinates with borders
		stack[i+0] += bs;
		stack[i+1] += bs;
	}
	
}
Beispiel #20
0
static bool mergeAndFilterLayerRegions(rcContext* ctx, int minRegionArea,
									   unsigned short& maxRegionId,
									   rcCompactHeightfield& chf,
									   unsigned short* srcReg, rcIntArray& overlaps)
{
	const int w = chf.width;
	const int h = chf.height;
	
	const int nreg = maxRegionId+1;
	rcRegion* regions = (rcRegion*)rcAlloc(sizeof(rcRegion)*nreg, RC_ALLOC_TEMP);
	if (!regions)
	{
		ctx->log(RC_LOG_ERROR, "mergeAndFilterLayerRegions: Out of memory 'regions' (%d).", nreg);
		return false;
	}
	
	// Construct regions
	for (int i = 0; i < nreg; ++i)
		new(&regions[i]) rcRegion((unsigned short)i);
	
	// Find region neighbours and overlapping regions.
	rcIntArray lregs(32);
	for (int y = 0; y < h; ++y)
	{
		for (int x = 0; x < w; ++x)
		{
			const rcCompactCell& c = chf.cells[x+y*w];

			lregs.resize(0);
			
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				const unsigned short ri = srcReg[i];
				if (ri == 0 || ri >= nreg) continue;
				rcRegion& reg = regions[ri];
				
				reg.spanCount++;
				
				reg.ymin = rcMin(reg.ymin, s.y);
				reg.ymax = rcMax(reg.ymax, s.y);
				
				// Collect all region layers.
				lregs.push(ri);
				
				// Update neighbours
				for (int dir = 0; dir < 4; ++dir)
				{
					if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
					{
						const int ax = x + rcGetDirOffsetX(dir);
						const int ay = y + rcGetDirOffsetY(dir);
						const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
						const unsigned short rai = srcReg[ai];
						if (rai > 0 && rai < nreg && rai != ri)
							addUniqueConnection(reg, rai);
						if (rai & RC_BORDER_REG)
							reg.connectsToBorder = true;
					}
				}
				
			}
			
			// Update overlapping regions.
			for (int i = 0; i < lregs.size()-1; ++i)
			{
				for (int j = i+1; j < lregs.size(); ++j)
				{
					if (lregs[i] != lregs[j])
					{
						rcRegion& ri = regions[lregs[i]];
						rcRegion& rj = regions[lregs[j]];
						addUniqueFloorRegion(ri, lregs[j]);
						addUniqueFloorRegion(rj, lregs[i]);
					}
				}
			}
			
		}
	}

	// Create 2D layers from regions.
	unsigned short layerId = 1;

	for (int i = 0; i < nreg; ++i)
		regions[i].id = 0;

	// Merge montone regions to create non-overlapping areas.
	rcIntArray stack(32);
	for (int i = 1; i < nreg; ++i)
	{
		rcRegion& root = regions[i];
		// Skip already visited.
		if (root.id != 0)
			continue;
		
		// Start search.
		root.id = layerId;

		stack.resize(0);
		stack.push(i);
		
		while (stack.size() > 0)
		{
			// Pop front
			rcRegion& reg = regions[stack[0]];
			for (int j = 0; j < stack.size()-1; ++j)
				stack[j] = stack[j+1];
			stack.resize(stack.size()-1);
			
			const int ncons = (int)reg.connections.size();
			for (int j = 0; j < ncons; ++j)
			{
				const int nei = reg.connections[j];
				rcRegion& regn = regions[nei];
				// Skip already visited.
				if (regn.id != 0)
					continue;
				// Skip if the neighbour is overlapping root region.
				bool overlap = false;
				for (int k = 0; k < root.floors.size(); k++)
				{
					if (root.floors[k] == nei)
					{
						overlap = true;
						break;
					}
				}
				if (overlap)
					continue;
					
				// Deepen
				stack.push(nei);
					
				// Mark layer id
				regn.id = layerId;
				// Merge current layers to root.
				for (int k = 0; k < regn.floors.size(); ++k)
					addUniqueFloorRegion(root, regn.floors[k]);
				root.ymin = rcMin(root.ymin, regn.ymin);
				root.ymax = rcMax(root.ymax, regn.ymax);
				root.spanCount += regn.spanCount;
				regn.spanCount = 0;
				root.connectsToBorder = root.connectsToBorder || regn.connectsToBorder;
			}
		}
		
		layerId++;
	}
	
	// Remove small regions
	for (int i = 0; i < nreg; ++i)
	{
		if (regions[i].spanCount > 0 && regions[i].spanCount < minRegionArea && !regions[i].connectsToBorder)
		{
			unsigned short reg = regions[i].id;
			for (int j = 0; j < nreg; ++j)
				if (regions[j].id == reg)
					regions[j].id = 0;
		}
	}
	
	// Compress region Ids.
	for (int i = 0; i < nreg; ++i)
	{
		regions[i].remap = false;
		if (regions[i].id == 0) continue;				// Skip nil regions.
		if (regions[i].id & RC_BORDER_REG) continue;    // Skip external regions.
		regions[i].remap = true;
	}
	
	unsigned short regIdGen = 0;
	for (int i = 0; i < nreg; ++i)
	{
		if (!regions[i].remap)
			continue;
		unsigned short oldId = regions[i].id;
		unsigned short newId = ++regIdGen;
		for (int j = i; j < nreg; ++j)
		{
			if (regions[j].id == oldId)
			{
				regions[j].id = newId;
				regions[j].remap = false;
			}
		}
	}
	maxRegionId = regIdGen;
	
	// Remap regions.
	for (int i = 0; i < chf.spanCount; ++i)
	{
		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;
}
Beispiel #21
0
bool rcMedianFilterWalkableArea(rcContext* ctx, rcCompactHeightfield& chf)
{
    rcAssert(ctx);
    
    const int w = chf.width;
    const int h = chf.height;
    
    ctx->startTimer(RC_TIMER_MEDIAN_AREA);
    
    unsigned char* areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP);
    if (!areas)
    {
        ctx->log(RC_LOG_ERROR, "medianFilterWalkableArea: Out of memory 'areas' (%d).", chf.spanCount);
        return false;
    }
    
    // Init distance.
    memset(areas, 0xff, sizeof(unsigned char)*chf.spanCount);
    
    for (int y = 0; y < h; ++y)
    {
        for (int x = 0; x < w; ++x)
        {
            const rcCompactCell& c = chf.cells[x+y*w];
            for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
            {
                const rcCompactSpan& s = chf.spans[i];
                if (chf.areas[i] == RC_NULL_AREA)
                {
                    areas[i] = chf.areas[i];
                    continue;
                }
                
                unsigned char nei[9];
                for (int j = 0; j < 9; ++j)
                    nei[j] = chf.areas[i];
                
                for (int dir = 0; dir < 4; ++dir)
                {
                    if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
                    {
                        const int ax = x + rcGetDirOffsetX(dir);
                        const int ay = y + rcGetDirOffsetY(dir);
                        const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
                        if (chf.areas[ai] != RC_NULL_AREA)
                            nei[dir*2+0] = chf.areas[ai];
                        
                        const rcCompactSpan& as = chf.spans[ai];
                        const int dir2 = (dir+1) & 0x3;
                        if (rcGetCon(as, dir2) != RC_NOT_CONNECTED)
                        {
                            const int ax2 = ax + rcGetDirOffsetX(dir2);
                            const int ay2 = ay + rcGetDirOffsetY(dir2);
                            const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);
                            if (chf.areas[ai2] != RC_NULL_AREA)
                                nei[dir*2+1] = chf.areas[ai2];
                        }
                    }
                }
                insertSort(nei, 9);
                areas[i] = nei[4];
            }
        }
    }
    
    memcpy(chf.areas, areas, sizeof(unsigned char)*chf.spanCount);
    
    rcFree(areas);

    ctx->stopTimer(RC_TIMER_MEDIAN_AREA);
    
    return true;
}
static void getHeightData(const rcCompactHeightfield& chf,
						  const unsigned short* poly, const int npoly,
						  const unsigned short* verts,
						  rcHeightPatch& hp, rcIntArray& stack)
{
	// Floodfill the heightfield to get 2D height data,
	// starting at vertex locations as seeds.

	memset(hp.data, 0, sizeof(unsigned short)*hp.width*hp.height);

	stack.resize(0);

	static const int offset[9*2] =
	{
		0,0, -1,-1, 0,-1, 1,-1, 1,0, 1,1, 0,1, -1,1, -1,0,
	};

	// Use poly vertices as seed points for the flood fill.
	for (int j = 0; j < npoly; ++j)
	{
		int cx = 0, cz = 0, ci =-1;
		int dmin = RC_UNSET_HEIGHT;
		for (int k = 0; k < 9; ++k)
		{
			const int ax = (int)verts[poly[j]*3+0] + offset[k*2+0];
			const int ay = (int)verts[poly[j]*3+1];
			const int az = (int)verts[poly[j]*3+2] + offset[k*2+1];
			if (ax < hp.xmin || ax >= hp.xmin+hp.width ||
				az < hp.ymin || az >= hp.ymin+hp.height)
				continue;

			const rcCompactCell& c = chf.cells[ax+az*chf.width];
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				int d = rcAbs(ay - (int)s.y);
				if (d < dmin)
				{
					cx = ax;
					cz = az;
					ci = i;
					dmin = d;
				}
			}
		}
		if (ci != -1)
		{
			stack.push(cx);
			stack.push(cz);
			stack.push(ci);
		}
	}

	// Find center of the polygon using flood fill.
	int pcx = 0, pcz = 0;
	for (int j = 0; j < npoly; ++j)
	{
		pcx += (int)verts[poly[j]*3+0];
		pcz += (int)verts[poly[j]*3+2];
	}
	pcx /= npoly;
	pcz /= npoly;

	for (int i = 0; i < stack.size(); i += 3)
	{
		int cx = stack[i+0];
		int cy = stack[i+1];
		int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width;
		hp.data[idx] = 1;
	}

	while (stack.size() > 0)
	{
		int ci = stack.pop();
		int cy = stack.pop();
		int cx = stack.pop();

		// Check if close to center of the polygon.
		if (rcAbs(cx-pcx) <= 1 && rcAbs(cy-pcz) <= 1)
		{
			stack.resize(0);
			stack.push(cx);
			stack.push(cy);
			stack.push(ci);
			break;
		}

		const rcCompactSpan& cs = chf.spans[ci];

		for (int dir = 0; dir < 4; ++dir)
		{
			if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue;

			const int ax = cx + rcGetDirOffsetX(dir);
			const int ay = cy + rcGetDirOffsetY(dir);

			if (ax < hp.xmin || ax >= (hp.xmin+hp.width) ||
				ay < hp.ymin || ay >= (hp.ymin+hp.height))
				continue;

			if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0)
				continue;

			const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(cs, dir);

			int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width;
			hp.data[idx] = 1;

			stack.push(ax);
			stack.push(ay);
			stack.push(ai);
		}
	}

	memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);

	// Mark start locations.
	for (int i = 0; i < stack.size(); i += 3)
	{
		int cx = stack[i+0];
		int cy = stack[i+1];
		int ci = stack[i+2];
		int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width;
		const rcCompactSpan& cs = chf.spans[ci];
		hp.data[idx] = cs.y;
	}

	static const int RETRACT_SIZE = 256;
	int head = 0;

	while (head*3 < stack.size())
	{
		int cx = stack[head*3+0];
		int cy = stack[head*3+1];
		int ci = stack[head*3+2];
		head++;
		if (head >= RETRACT_SIZE)
		{
			head = 0;
			if (stack.size() > RETRACT_SIZE*3)
				memmove(&stack[0], &stack[RETRACT_SIZE*3], sizeof(int)*(stack.size()-RETRACT_SIZE*3));
			stack.resize(stack.size()-RETRACT_SIZE*3);
		}

		const rcCompactSpan& cs = chf.spans[ci];
		for (int dir = 0; dir < 4; ++dir)
		{
			if (rcGetCon(cs, dir) == RC_NOT_CONNECTED) continue;

			const int ax = cx + rcGetDirOffsetX(dir);
			const int ay = cy + rcGetDirOffsetY(dir);

			if (ax < hp.xmin || ax >= (hp.xmin+hp.width) ||
				ay < hp.ymin || ay >= (hp.ymin+hp.height))
				continue;

			if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != RC_UNSET_HEIGHT)
				continue;

			const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(cs, dir);

			const rcCompactSpan& as = chf.spans[ai];
			int idx = ax-hp.xmin+(ay-hp.ymin)*hp.width;
			hp.data[idx] = as.y;

			stack.push(ax);
			stack.push(ay);
			stack.push(ai);
		}
	}
}
/// @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;
}
Beispiel #24
0
/// @par
/// 
/// See the #rcConfig documentation for more information on the configuration parameters.
/// 
/// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig
bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf,
	const int borderSize, const int walkableHeight,
	rcHeightfieldLayerSet& lset)
{
	rcAssert(ctx);

	ctx->startTimer(RC_TIMER_BUILD_LAYERS);

	rcScopedDelete<unsigned short> spanBuf4 = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP);
	if (!spanBuf4)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: 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);

	const int w = chf.width;
	const int h = chf.height;
	const int nreg = chf.maxRegions + 1;
	rcLayerRegion* regions = (rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nreg, RC_ALLOC_TEMP);
	if (!regions)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regions' (%d).", nreg);
		return false;
	}

	// Construct regions
	memset(regions, 0, sizeof(rcLayerRegion)*nreg);
	for (int i = 0; i < nreg; ++i)
	{
		regions[i].layerId = (unsigned short)i;
		regions[i].ymax = 0;
		regions[i].ymin = 0xffff;
	}

	// Find region neighbours and overlapping regions.
	for (int y = 0; y < h; ++y)
	{
		for (int x = 0; x < w; ++x)
		{
			const rcCompactCell& c = chf.cells[x+y*w];
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				const unsigned short ri = srcReg[i];
				if (ri == 0 || ri >= nreg)
					continue;

				rcLayerRegion& reg = regions[ri];
				reg.ymin = rcMin(reg.ymin, s.y);
				reg.ymax = rcMax(reg.ymax, s.y);
				reg.hasSpans = true;

				// Collect all region layers.
				for (int j = (int)c.index; j < ni; ++j)
				{
					unsigned short nri = srcReg[j];
					if (nri == 0 || nri >= nreg)
						continue;

					if (nri != ri)
					{
						addUniqueLayerRegion(reg, nri);
					}
				}

				// Have found contour
				if (reg.connections.size() > 0)
					continue;

				// 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 neighbors.
					walkContour(x, y, i, ndir, chf, srcReg, reg.connections);
				}
			}
		}
	} 

	// Create 2D layers from regions. 
	unsigned short layerId = 0;
	rcIntArray stack(64);
	for (int i = 0; i < nreg; i++)
	{
		rcLayerRegion& reg = regions[i];
		if (reg.visited || !reg.hasSpans)
			continue;

		reg.layerId = layerId;
		reg.visited = true;
		reg.base = true;

		stack.resize(0);
		stack.push(i);

		while (stack.size())
		{
			int ri = stack.pop();
			rcLayerRegion& creg = regions[ri];
			for (int j = 0; j < creg.connections.size(); j++)
			{
				const unsigned short nei = (unsigned short)creg.connections[j];
				if (nei & RC_BORDER_REG)
					continue;

				rcLayerRegion& regn = regions[nei];
				// Skip already visited.
				if (regn.visited)
					continue;
				// Skip if the neighbor is overlapping root region.
				if (reg.layers.contains(nei))
					continue;
				// Skip if the height range would become too large.
				const int ymin = rcMin(reg.ymin, regn.ymin);
				const int ymax = rcMin(reg.ymax, regn.ymax);
				if ((ymax - ymin) >= 255)
					continue;

				// visit
				stack.push(nei);
				regn.visited = true;
				regn.layerId = layerId;
				// add layers to root
				for (int k = 0; k < regn.layers.size(); k++)
					addUniqueLayerRegion(reg, regn.layers[k]);
				reg.ymin = rcMin(reg.ymin, regn.ymin);
				reg.ymax = rcMax(reg.ymax, regn.ymax);
			}
		}

		layerId++;
	}

	// Merge non-overlapping regions that are close in height.
	const unsigned short mergeHeight = (unsigned short)walkableHeight * 4; 
	for (int i = 0; i < nreg; i++)
	{
		rcLayerRegion& ri = regions[i];
		if (!ri.base) continue;

		unsigned short newId = ri.layerId;
		for (;;)
		{
			unsigned short oldId = 0xffff;
			for (int j = 0; j < nreg; j++)
			{
				if (i == j) continue;
				rcLayerRegion& rj = regions[j];
				if (!rj.base) continue;

				// Skip if the regions are not close to each other.
				if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight))
					continue;
				// Skip if the height range would become too large.
				const int ymin = rcMin(ri.ymin, rj.ymin);
				const int ymax = rcMin(ri.ymax, rj.ymax);
				if ((ymax - ymin) >= 255)
					continue;

				// Make sure that there is no overlap when mergin 'ri' and 'rj'.
				bool overlap = false;
				// Iterate over all regions which have the same layerId as 'rj'
				for (int k = 0; k < nreg; ++k)
				{
					if (regions[k].layerId != rj.layerId)
						continue;
					// Check if region 'k' is overlapping region 'ri'
					// Index to 'regs' is the same as region id.
					if (ri.layers.contains(k))
					{
						overlap = true;
						break;
					}
				}
				// Cannot merge of regions overlap.
				if (overlap)
					continue;

				// Can merge i and j.
				oldId = rj.layerId;
				break;
			}

			// Could not find anything to merge with, stop.
			if (oldId == 0xffff)
				break;

			// Merge
			for (int j = 0; j < nreg; ++j)
			{
				rcLayerRegion& rj = regions[j];
				if (rj.layerId == oldId)
				{
					rj.base = 0;
					// Remap layerIds.
					rj.layerId = newId;
					// Add overlaid layers from 'rj' to 'ri'.
					for (int k = 0; k < rj.layers.size(); ++k)
						addUniqueLayerRegion(ri, rj.layers[k]);
					// Update height bounds.
					ri.ymin = rcMin(ri.ymin, rj.ymin);
					ri.ymax = rcMax(ri.ymax, rj.ymax);
				}
			}
		}
	}

	// Compress layer Ids.
	for (int i = 0; i < nreg; ++i)
	{
		regions[i].remap = regions[i].hasSpans;
		if (!regions[i].hasSpans)
		{
			regions[i].layerId = 0xffff;
		}
	}

	unsigned short maxLayerId = 0;
	for (int i = 0; i < nreg; ++i)
	{
		if (!regions[i].remap)
			continue;
		unsigned short oldId = regions[i].layerId;
		unsigned short newId = maxLayerId;
		for (int j = i; j < nreg; ++j)
		{
			if (regions[j].layerId == oldId)
			{
				regions[j].layerId = newId;
				regions[j].remap = false;
			}
		}
		maxLayerId++;
	}

	ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER);

	if (maxLayerId == 0)
	{
		ctx->stopTimer(RC_TIMER_BUILD_LAYERS);
		return true;
	}

	// Create layers.
	rcAssert(lset.layers == 0);

	const int lw = w - borderSize*2;
	const int lh = h - borderSize*2;

	// Build contracted bbox for layers.
	float bmin[3], bmax[3];
	rcVcopy(bmin, chf.bmin);
	rcVcopy(bmax, chf.bmax);
	bmin[0] += borderSize*chf.cs;
	bmin[2] += borderSize*chf.cs;
	bmax[0] -= borderSize*chf.cs;
	bmax[2] -= borderSize*chf.cs;

	lset.nlayers = (int)maxLayerId;

	lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM);
	if (!lset.layers)
	{
		ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers);
		return false;
	}
	memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers);


	// Store layers.
	for (int i = 0; i < lset.nlayers; ++i)
	{
		unsigned short curId = (unsigned short)i;

		// Allocate memory for the current layer.
		rcHeightfieldLayer* layer = &lset.layers[i];
		memset(layer, 0, sizeof(rcHeightfieldLayer));

		const int gridSize = sizeof(unsigned char)*lw*lh;

		layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
		if (!layer->heights)
		{
			ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize);
			return false;
		}
		memset(layer->heights, 0xff, gridSize);

		layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
		if (!layer->areas)
		{
			ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize);
			return false;
		}
		memset(layer->areas, 0, gridSize);

		layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
		if (!layer->cons)
		{
			ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize);
			return false;
		}
		memset(layer->cons, 0, gridSize);

		// Find layer height bounds.
		int hmin = 0, hmax = 0;
		for (int j = 0; j < nreg; ++j)
		{
			if (regions[j].base && regions[j].layerId == curId)
			{
				hmin = (int)regions[j].ymin;
				hmax = (int)regions[j].ymax;
			}
		}

		layer->width = lw;
		layer->height = lh;
		layer->cs = chf.cs;
		layer->ch = chf.ch;

		// Adjust the bbox to fit the heighfield.
		rcVcopy(layer->bmin, bmin);
		rcVcopy(layer->bmax, bmax);
		layer->bmin[1] = bmin[1] + hmin*chf.ch;
		layer->bmax[1] = bmin[1] + hmax*chf.ch;
		layer->hmin = hmin;
		layer->hmax = hmax;

		// Update usable data region.
		layer->minx = layer->width;
		layer->maxx = 0;
		layer->miny = layer->height;
		layer->maxy = 0;

		// Copy height and area from compact heighfield. 
		for (int y = 0; y < lh; ++y)
		{
			for (int x = 0; x < lw; ++x)
			{
				const int cx = borderSize+x;
				const int cy = borderSize+y;
				const rcCompactCell& c = chf.cells[cx+cy*w];
				for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j)
				{
					const rcCompactSpan& s = chf.spans[j];
					// Skip unassigned regions.
					if (srcReg[j] == 0 || srcReg[j] >= nreg)
						continue;
					// Skip of does not belong to current layer.
					unsigned short lid = regions[srcReg[j]].layerId;
					if (lid != curId)
						continue;

					// Update data bounds.
					layer->minx = rcMin(layer->minx, x);
					layer->maxx = rcMax(layer->maxx, x);
					layer->miny = rcMin(layer->miny, y);
					layer->maxy = rcMax(layer->maxy, y);

					// Store height and area type.
					const int idx = x+y*lw;
					layer->heights[idx] = (unsigned char)(s.y - hmin);
					layer->areas[idx] = chf.areas[j];

					// Check connection.
					unsigned char portal = 0;
					unsigned char con = 0;
					for (int dir = 0; dir < 4; ++dir)
					{
						if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
						{
							const int ax = cx + rcGetDirOffsetX(dir);
							const int ay = cy + rcGetDirOffsetY(dir);
							const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
							unsigned short alid = (srcReg[ai] < nreg) ? regions[srcReg[ai]].layerId : 0xffff;
							// Portal mask
							if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
							{
								portal |= (unsigned char)(1<<dir);
								// Update height so that it matches on both sides of the portal.
								const rcCompactSpan& as = chf.spans[ai];
								if (as.y > hmin)
									layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin));
							}
							// Valid connection mask
							if (chf.areas[ai] != RC_NULL_AREA && lid == alid)
							{
								const int nx = ax - borderSize;
								const int ny = ay - borderSize;
								if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
									con |= (unsigned char)(1<<dir);
							}
						}
					}

					layer->cons[idx] = (portal << 4) | con;
				}
			}
		}

		if (layer->minx > layer->maxx)
			layer->minx = layer->maxx = 0;
		if (layer->miny > layer->maxy)
			layer->miny = layer->maxy = 0;
	}

	ctx->stopTimer(RC_TIMER_BUILD_LAYERS);
	return true;
}
static bool floodRegion(int x, int y, int i,
                        unsigned short level, unsigned short r,
                        rcCompactHeightfield& chf,
                        unsigned short* srcReg, unsigned short* srcDist,
                        rcIntArray& stack)
{
    const int w = chf.width;
    
    const unsigned char area = chf.areas[i];
    
    // Flood fill mark region.
    stack.resize(0);
    stack.push((int)x);
    stack.push((int)y);
    stack.push((int)i);
    srcReg[i] = r;
    srcDist[i] = 0;
    
    unsigned short lev = level >= 2 ? level-2 : 0;
    int count = 0;
    
    while (stack.size() > 0)
    {
        int ci = stack.pop();
        int cy = stack.pop();
        int cx = stack.pop();
        
        const rcCompactSpan& cs = chf.spans[ci];
        
        // Check if any of the neighbours already have a valid region set.
        unsigned short ar = 0;
        for (int dir = 0; dir < 4; ++dir)
        {
            // 8 connected
            if (rcGetCon(cs, dir) != RC_NOT_CONNECTED)
            {
                const int ax = cx + rcGetDirOffsetX(dir);
                const int ay = cy + rcGetDirOffsetY(dir);
                const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);
                if (chf.areas[ai] != area)
                    continue;
                unsigned short nr = srcReg[ai];
                if (nr & RC_BORDER_REG) // Do not take borders into account.
                    continue;
                if (nr != 0 && nr != r)
                    ar = nr;
                
                const rcCompactSpan& as = chf.spans[ai];
                
                const int dir2 = (dir+1) & 0x3;
                if (rcGetCon(as, dir2) != RC_NOT_CONNECTED)
                {
                    const int ax2 = ax + rcGetDirOffsetX(dir2);
                    const int ay2 = ay + rcGetDirOffsetY(dir2);
                    const int ai2 = (int)chf.cells[ax2+ay2*w].index + rcGetCon(as, dir2);
                    if (chf.areas[ai2] != area)
                        continue;
                    unsigned short nr2 = srcReg[ai2];
                    if (nr2 != 0 && nr2 != r)
                        ar = nr2;
                }                
            }
        }
        if (ar != 0)
        {
            srcReg[ci] = 0;
            continue;
        }
        count++;
        
        // Expand neighbours.
        for (int dir = 0; dir < 4; ++dir)
        {
            if (rcGetCon(cs, dir) != RC_NOT_CONNECTED)
            {
                const int ax = cx + rcGetDirOffsetX(dir);
                const int ay = cy + rcGetDirOffsetY(dir);
                const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(cs, dir);
                if (chf.areas[ai] != area)
                    continue;
                if (chf.dist[ai] >= lev && srcReg[ai] == 0)
                {
                    srcReg[ai] = r;
                    srcDist[ai] = 0;
                    stack.push(ax);
                    stack.push(ay);
                    stack.push(ai);
                }
            }
        }
    }
    
    return count > 0;
}
Beispiel #26
0
static bool SplitAndStoreLayerRegions(rcContext* ctx, rcCompactHeightfield& chf,
									  const int borderSize, const int walkableHeight,
									  unsigned short* srcReg, rcLayerRegionMonotone* regs, const int nregs,
									  rcHeightfieldLayerSet& lset)
{
	// Create 2D layers from regions.
	unsigned short layerId = 0;

	rcIntArray stack(64);
	stack.resize(0);

	for (int i = 0; i < nregs; ++i)
	{
		rcLayerRegionMonotone& root = regs[i];
		// Skip already visited.
		if (root.layerId != 0xffff)
			continue;

		// Start search.
		root.layerId = layerId;
		root.base = 1;
		stack.push(i);

		while (stack.size())
		{
			// Pop front
			rcLayerRegionMonotone& reg = regs[stack[0]];
			for (int j = 1; j < stack.size(); ++j)
				stack[j - 1] = stack[j];
			stack.pop();

			const int nneis = (int)reg.neis.size();
			for (int j = 0; j < nneis; ++j)
			{
				const int nei = reg.neis[j];
				rcLayerRegionMonotone& regn = regs[nei];
				// Skip already visited.
				if (regn.layerId != 0xffff)
					continue;
				// Skip if the neighbour is overlapping root region.
				if (root.layers.contains(nei))
					continue;
				// Skip if the height range would become too large.
				const int ymin = rcMin(root.ymin, regn.ymin);
				const int ymax = rcMin(root.ymax, regn.ymax);
				if ((ymax - ymin) >= 255)
					continue;

				// Deepen
				stack.push(nei);

				// Mark layer id
				regn.layerId = layerId;
				// Merge current layers to root.
				for (int k = 0; k < regn.layers.size(); ++k)
					addUnique(root.layers, regn.layers[k]);
				root.ymin = rcMin(root.ymin, regn.ymin);
				root.ymax = rcMax(root.ymax, regn.ymax);
			}
		}

		layerId++;
	}

	// Merge non-overlapping regions that are close in height.
	const unsigned short mergeHeight = (unsigned short)walkableHeight * 4;

	for (int i = 0; i < nregs; ++i)
	{
		rcLayerRegionMonotone& ri = regs[i];
		if (!ri.base) continue;

		unsigned short newId = ri.layerId;

		for (;;)
		{
			unsigned short oldId = 0xffff;

			for (int j = 0; j < nregs; ++j)
			{
				if (i == j) continue;
				rcLayerRegionMonotone& rj = regs[j];
				if (!rj.base) continue;

				// Skip if the regions are not close to each other.
				if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight))
					continue;
				// Skip if the height range would become too large.
				const int ymin = rcMin(ri.ymin, rj.ymin);
				const int ymax = rcMin(ri.ymax, rj.ymax);
				if ((ymax - ymin) >= 255)
					continue;

				// Make sure that there is no overlap when mergin 'ri' and 'rj'.
				bool overlap = false;
				// Iterate over all regions which have the same layerId as 'rj'
				for (int k = 0; k < nregs; ++k)
				{
					if (regs[k].layerId != rj.layerId)
						continue;
					// Check if region 'k' is overlapping region 'ri'
					// Index to 'regs' is the same as region id.
					if (ri.layers.contains(k))
					{
						overlap = true;
						break;
					}
				}
				// Cannot merge of regions overlap.
				if (overlap)
					continue;

				// Can merge i and j.
				oldId = rj.layerId;
				break;
			}

			// Could not find anything to merge with, stop.
			if (oldId == 0xffff)
				break;

			// Merge
			for (int j = 0; j < nregs; ++j)
			{
				rcLayerRegionMonotone& rj = regs[j];
				if (rj.layerId == oldId)
				{
					rj.base = 0;
					// Remap layerIds.
					rj.layerId = newId;
					// Add overlaid layers from 'rj' to 'ri'.
					for (int k = 0; k < rj.layers.size(); ++k)
						addUnique(ri.layers, rj.layers[k]);
					// Update heigh bounds.
					ri.ymin = rcMin(ri.ymin, rj.ymin);
					ri.ymax = rcMax(ri.ymax, rj.ymax);
				}
			}
		}
	}

	// Compact layerIds
	layerId = 0;
	if (nregs < 256)
	{
		// Compact ids.
		unsigned short remap[256];
		memset(remap, 0, sizeof(unsigned short)*256);
		// Find number of unique regions.
		for (int i = 0; i < nregs; ++i)
			remap[regs[i].layerId] = 1;
		for (int i = 0; i < 256; ++i)
			if (remap[i])
				remap[i] = layerId++;
		// Remap ids.
		for (int i = 0; i < nregs; ++i)
			regs[i].layerId = remap[regs[i].layerId];
	}
	else
	{
		for (int i = 0; i < nregs; ++i)
			regs[i].remap = true;

		for (int i = 0; i < nregs; ++i)
		{
			if (!regs[i].remap)
				continue;
			unsigned short oldId = regs[i].layerId;
			unsigned short newId = ++layerId;
			for (int j = i; j < nregs; ++j)
			{
				if (regs[j].layerId == oldId)
				{
					regs[j].layerId = newId;
					regs[j].remap = false;
				}
			}
		}
	}

	// No layers, return empty.
	if (layerId == 0)
	{
		ctx->stopTimer(RC_TIMER_BUILD_LAYERS);
		return true;
	}

	// Create layers.
	rcAssert(lset.layers == 0);

	const int w = chf.width;
	const int h = chf.height;
	const int lw = w - borderSize*2;
	const int lh = h - borderSize*2;

	// Build contracted bbox for layers.
	float bmin[3], bmax[3];
	rcVcopy(bmin, chf.bmin);
	rcVcopy(bmax, chf.bmax);
	bmin[0] += borderSize*chf.cs;
	bmin[2] += borderSize*chf.cs;
	bmax[0] -= borderSize*chf.cs;
	bmax[2] -= borderSize*chf.cs;

	lset.nlayers = (int)layerId;

	lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM);
	if (!lset.layers)
	{
		ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'layers' (%d).", lset.nlayers);
		return false;
	}
	memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers);


	// Store layers.
	for (int i = 0; i < lset.nlayers; ++i)
	{
		unsigned short curId = (unsigned short)i;

		// Allocate memory for the current layer.
		rcHeightfieldLayer* layer = &lset.layers[i];
		memset(layer, 0, sizeof(rcHeightfieldLayer));

		const int gridSize = sizeof(unsigned char)*lw*lh;

		layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
		if (!layer->heights)
		{
			ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'heights' (%d).", gridSize);
			return false;
		}
		memset(layer->heights, 0xff, gridSize);

		layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
		if (!layer->areas)
		{
			ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'areas' (%d).", gridSize);
			return false;
		}
		memset(layer->areas, 0, gridSize);

		layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM);
		if (!layer->cons)
		{
			ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'cons' (%d).", gridSize);
			return false;
		}
		memset(layer->cons, 0, gridSize);

		// Find layer height bounds.
		int hmin = 0, hmax = 0;
		for (int j = 0; j < nregs; ++j)
		{
			if (regs[j].base && regs[j].layerId == curId)
			{
				hmin = (int)regs[j].ymin;
				hmax = (int)regs[j].ymax;
			}
		}

		layer->width = lw;
		layer->height = lh;
		layer->cs = chf.cs;
		layer->ch = chf.ch;

		// Adjust the bbox to fit the heighfield.
		rcVcopy(layer->bmin, bmin);
		rcVcopy(layer->bmax, bmax);
		layer->bmin[1] = bmin[1] + hmin*chf.ch;
		layer->bmax[1] = bmin[1] + hmax*chf.ch;
		layer->hmin = hmin;
		layer->hmax = hmax;

		// Update usable data region.
		layer->minx = layer->width;
		layer->maxx = 0;
		layer->miny = layer->height;
		layer->maxy = 0;

		// Copy height and area from compact heighfield. 
		for (int y = 0; y < lh; ++y)
		{
			for (int x = 0; x < lw; ++x)
			{
				const int cx = borderSize+x;
				const int cy = borderSize+y;
				const rcCompactCell& c = chf.cells[cx+cy*w];
				for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j)
				{
					const rcCompactSpan& s = chf.spans[j];
					// Skip unassigned regions.
					if (srcReg[j] == 0xffff)
						continue;
					// Skip of does nto belong to current layer.
					unsigned short lid = regs[srcReg[j]].layerId;
					if (lid != curId)
						continue;

					// Update data bounds.
					layer->minx = rcMin(layer->minx, x);
					layer->maxx = rcMax(layer->maxx, x);
					layer->miny = rcMin(layer->miny, y);
					layer->maxy = rcMax(layer->maxy, y);

					// Store height and area type.
					const int idx = x+y*lw;
					layer->heights[idx] = (unsigned char)(s.y - hmin);
					layer->areas[idx] = chf.areas[j];

					// Check connection.
					unsigned char portal = 0;
					unsigned char con = 0;
					for (int dir = 0; dir < 4; ++dir)
					{
						if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
						{
							const int ax = cx + rcGetDirOffsetX(dir);
							const int ay = cy + rcGetDirOffsetY(dir);
							const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
							unsigned short alid = srcReg[ai] != 0xffff ? regs[srcReg[ai]].layerId : 0xffff;
							// Portal mask
							if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
							{
								portal |= (unsigned char)(1<<dir);
								// Update height so that it matches on both sides of the portal.
								const rcCompactSpan& as = chf.spans[ai];
								if (as.y > hmin)
									layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin));
							}
							// Valid connection mask
							if (chf.areas[ai] != RC_NULL_AREA && lid == alid)
							{
								const int nx = ax - borderSize;
								const int ny = ay - borderSize;
								if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
								{
									con |= (unsigned char)(1 << dir);

									// [UE4: make sure that connections are bidirectional, otherwise contour tracing will stuck in infinite loop]
									const int nidx = nx + (ny * lw);
									layer->cons[nidx] |= (unsigned char)(1 << ((dir + 2) % 4));
								}
							}
						}
					}

					layer->cons[idx] |= (portal << 4) | con;
				}
			}
		}

		if (layer->minx > layer->maxx)
			layer->minx = layer->maxx = 0;
		if (layer->miny > layer->maxy)
			layer->miny = layer->maxy = 0;
	}

	return true;
}
static unsigned short* expandRegions(int maxIter, unsigned short level,
                                     rcCompactHeightfield& chf,
                                     unsigned short* srcReg, unsigned short* srcDist,
                                     unsigned short* dstReg, unsigned short* dstDist, 
                                     rcIntArray& stack)
{
    const int w = chf.width;
    const int h = chf.height;

    // Find cells revealed by the raised level.
    stack.resize(0);
    for (int y = 0; y < h; ++y)
    {
        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)
                {
                    stack.push(x);
                    stack.push(y);
                    stack.push(i);
                }
            }
        }
    }
    
    int iter = 0;
    while (stack.size() > 0)
    {
        int failed = 0;
        
        memcpy(dstReg, srcReg, sizeof(unsigned short)*chf.spanCount);
        memcpy(dstDist, srcDist, sizeof(unsigned short)*chf.spanCount);
        
        for (int j = 0; j < stack.size(); j += 3)
        {
            int x = stack[j+0];
            int y = stack[j+1];
            int i = stack[j+2];
            if (i < 0)
            {
                failed++;
                continue;
            }
            
            unsigned short r = srcReg[i];
            unsigned short d2 = 0xffff;
            const unsigned char area = chf.areas[i];
            const rcCompactSpan& s = chf.spans[i];
            for (int dir = 0; dir < 4; ++dir)
            {
                if (rcGetCon(s, dir) == RC_NOT_CONNECTED) continue;
                const int ax = x + rcGetDirOffsetX(dir);
                const int ay = y + rcGetDirOffsetY(dir);
                const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
                if (chf.areas[ai] != area) continue;
                if (srcReg[ai] > 0 && (srcReg[ai] & RC_BORDER_REG) == 0)
                {
                    if ((int)srcDist[ai]+2 < (int)d2)
                    {
                        r = srcReg[ai];
                        d2 = srcDist[ai]+2;
                    }
                }
            }
            if (r)
            {
                stack[j+2] = -1; // mark as used
                dstReg[i] = r;
                dstDist[i] = d2;
            }
            else
            {
                failed++;
            }
        }
        
        // rcSwap source and dest.
        rcSwap(srcReg, dstReg);
        rcSwap(srcDist, dstDist);
        
        if (failed*3 == stack.size())
            break;
        
        if (level > 0)
        {
            ++iter;
            if (iter >= maxIter)
                break;
        }
    }
    
    return srcReg;
}
Beispiel #28
0
static bool CollectLayerRegionsMonotone(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize,
										unsigned short* srcReg, rcLayerRegionMonotone*& regs, int& nregs)
{
	const int w = chf.width;
	const int h = chf.height;

	const int nsweeps = chf.width;
	rcScopedDelete<rcLayerSweepSpan> sweeps = (rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP);
	if (!sweeps)
	{
		ctx->log(RC_LOG_ERROR, "CollectLayerRegionsMonotone: Out of memory 'sweeps' (%d).", nsweeps);
		return false;
	}

	// Partition walkable area into monotone regions.
	rcIntArray prev(256);
	unsigned short regId = 0;

	for (int y = borderSize; y < h-borderSize; ++y)
	{
		prev.resize(regId+1);
		memset(&prev[0],0,sizeof(int)*regId);
		unsigned short sweepId = 0;

		for (int x = borderSize; x < w-borderSize; ++x)
		{
			const rcCompactCell& c = chf.cells[x+y*w];

			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				if (chf.areas[i] == RC_NULL_AREA) continue;

				unsigned short sid = 0xffff;

				// -x
				if (rcGetCon(s, 0) != RC_NOT_CONNECTED)
				{
					const int ax = x + rcGetDirOffsetX(0);
					const int ay = y + rcGetDirOffsetY(0);
					const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0);
					if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xffff)
						sid = srcReg[ai];
				}

				if (sid == 0xffff)
				{
					sid = sweepId++;
					sweeps[sid].nei = 0xffff;
					sweeps[sid].ns = 0;
				}

				// -y
				if (rcGetCon(s,3) != RC_NOT_CONNECTED)
				{
					const int ax = x + rcGetDirOffsetX(3);
					const int ay = y + rcGetDirOffsetY(3);
					const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3);
					const unsigned short nr = srcReg[ai];
					if (nr != 0xffff)
					{
						// Set neighbour when first valid neighbour is encoutered.
						if (sweeps[sid].ns == 0)
							sweeps[sid].nei = nr;

						if (sweeps[sid].nei == nr)
						{
							// Update existing neighbour
							sweeps[sid].ns++;
							prev[nr]++;
						}
						else
						{
							// This is hit if there is nore than one neighbour.
							// Invalidate the neighbour.
							sweeps[sid].nei = 0xffff;
						}
					}
				}

				srcReg[i] = sid;
			}
		}

		// Create unique ID.
		for (int i = 0; i < sweepId; ++i)
		{
			// If the neighbour is set and there is only one continuous connection to it,
			// the sweep will be merged with the previous one, else new region is created.
			if (sweeps[i].nei != 0xffff && prev[sweeps[i].nei] == sweeps[i].ns)
			{
				sweeps[i].id = sweeps[i].nei;
			}
			else
			{
				sweeps[i].id = regId++;
			}
		}

		// Remap local sweep ids to region ids.
		for (int x = borderSize; x < w-borderSize; ++x)
		{
			const rcCompactCell& c = chf.cells[x+y*w];
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				if (srcReg[i] != 0xffff)
					srcReg[i] = sweeps[srcReg[i]].id;
			}
		}
	}

	// Allocate and init layer regions.
	nregs = (int)regId;
	regs = (rcLayerRegionMonotone*)rcAlloc(sizeof(rcLayerRegionMonotone)*nregs, RC_ALLOC_TEMP);
	if (!regs)
	{
		ctx->log(RC_LOG_ERROR, "CollectLayerRegionsMonotone: Out of memory 'regs' (%d).", nregs);
		return false;
	}
	memset(regs, 0, sizeof(rcLayerRegionMonotone)*nregs);
	for (int i = 0; i < nregs; ++i)
	{
		regs[i].layerId = 0xffff;
		regs[i].ymin = 0xffff;
		regs[i].ymax = 0;
	}

	rcIntArray lregs(64);

	// Find region neighbours and overlapping regions.
	for (int y = 0; y < h; ++y)
	{
		for (int x = 0; x < w; ++x)
		{
			const rcCompactCell& c = chf.cells[x+y*w];
			lregs.resize(0);

			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				const rcCompactSpan& s = chf.spans[i];
				const unsigned short ri = srcReg[i];
				if (ri == 0xffff) continue;

				regs[ri].ymin = rcMin(regs[ri].ymin, s.y);
				regs[ri].ymax = rcMax(regs[ri].ymax, s.y);

				// Collect all region layers.
				lregs.push(ri);

				// Update neighbours
				for (int dir = 0; dir < 4; ++dir)
				{
					if (rcGetCon(s, dir) != RC_NOT_CONNECTED)
					{
						const int ax = x + rcGetDirOffsetX(dir);
						const int ay = y + rcGetDirOffsetY(dir);
						const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir);
						const unsigned short rai = srcReg[ai];
						if (rai != 0xffff && rai != ri)
							addUnique(regs[ri].neis, rai);
					}
				}

			}

			// Update overlapping regions.
			const int nlregs = lregs.size();
			for (int i = 0; i < nlregs-1; ++i)
			{
				for (int j = i+1; j < nlregs; ++j)
				{
					if (lregs[i] != lregs[j])
					{
						rcLayerRegionMonotone& ri = regs[lregs[i]];
						rcLayerRegionMonotone& rj = regs[lregs[j]];
						addUnique(ri.layers, lregs[j]);
						addUnique(rj.layers, lregs[i]);
					}
				}
			}
		}
	}

	return true;
}
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;
}
static void getHeightData(const rcCompactHeightfield& chf,
						  const unsigned short* poly, const int npoly,
						  const unsigned short* verts,
						  rcHeightPatch& hp, rcIntArray& stack)
{
	// Floodfill the heightfield to get 2D height data,
	// starting at vertex locations as seeds.
	
	memset(hp.data, 0xff, sizeof(unsigned short)*hp.width*hp.height);

	stack.resize(0);
	
	// Use poly vertices as seed points for the flood fill.
	for (int j = 0; j < npoly; ++j)
	{
		const int ax = (int)verts[poly[j]*3+0];
		const int ay = (int)verts[poly[j]*3+1];
		const int az = (int)verts[poly[j]*3+2];
		if (ax < hp.xmin || ax >= hp.xmin+hp.width ||
			az < hp.ymin || az >= hp.ymin+hp.height)
			continue;
			
		const rcCompactCell& c = chf.cells[ax+az*chf.width];
		int dmin = 0xffff;
		int ai = -1;
		for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
		{
			const rcCompactSpan& s = chf.spans[i];
			int d = rcAbs(ay - (int)s.y);
			if (d < dmin)
			{
				ai = i;
				dmin = d;
			}
		}
		if (ai != -1)
		{
			stack.push(ax);
			stack.push(az);
			stack.push(ai);
		}
	}

	while (stack.size() > 0)
	{
		int ci = stack.pop();
		int cy = stack.pop();
		int cx = stack.pop();

		// Skip already visited locations.
		int idx = cx-hp.xmin+(cy-hp.ymin)*hp.width;
		if (hp.data[idx] != 0xffff)
			continue;
		
		const rcCompactSpan& cs = chf.spans[ci];
		hp.data[idx] = cs.y;
		
		for (int dir = 0; dir < 4; ++dir)
		{
			if (rcGetCon(cs, dir) == 0xf) continue;
			
			const int ax = cx + rcGetDirOffsetX(dir);
			const int ay = cy + rcGetDirOffsetY(dir);
		
			if (ax < hp.xmin || ax >= (hp.xmin+hp.width) ||
				ay < hp.ymin || ay >= (hp.ymin+hp.height))
				continue;

			if (hp.data[ax-hp.xmin+(ay-hp.ymin)*hp.width] != 0xffff)
				continue;

			const int ai = (int)chf.cells[ax+ay*chf.width].index + rcGetCon(cs, dir);
			
			stack.push(ax);
			stack.push(ay);
			stack.push(ai);
		}
	}	
}