static bool filterSmallRegions(int minRegionSize, int mergeRegionSize,
							   unsigned short& maxRegionId,
							   rcCompactHeightfield& chf,
							   unsigned short* src)
{
	const int w = chf.width;
	const int h = chf.height;

	int nreg = maxRegionId+1;
	rcRegion* regions = new rcRegion[nreg];
	if (!regions)
	{
		if (rcGetLog())
			rcGetLog()->log(RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (%d).", nreg);
		return false;
	}
	
	for (int i = 0; i < nreg; ++i)
		regions[i].id = (unsigned short)i;

	// Find edge of a region and find connections around the contour.
	for (int y = 0; y < h; ++y)
	{
		for (int x = 0; x < w; ++x)
		{
			const rcCompactCell& c = chf.cells[x+y*w];
			for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
			{
				unsigned short r = src[i*2];
				if (r == 0 || r >= nreg)
					continue;
				
				rcRegion& reg = regions[r];
				reg.count++;
				

				// Update floors.
				for (int j = (int)c.index; j < ni; ++j)
				{
					if (i == j) continue;
					unsigned short floorId = src[j*2];
					if (floorId == 0 || floorId >= nreg)
						continue;
					addUniqueFloorRegion(reg, floorId);
				}
				
				// 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, src, x, y, i, dir))
					{
						ndir = dir;
						break;
					}
				}
				
				if (ndir != -1)
				{
					// The cell is at border.
					// Walk around the contour to find all the neighbours.
					walkContour(x, y, i, ndir, chf, src, reg.connections);
				}
			}
		}
	}
	
	// Remove too small unconnected regions.
	for (int i = 0; i < nreg; ++i)
	{
		rcRegion& reg = regions[i];
		if (reg.id == 0 || (reg.id & RC_BORDER_REG))
			continue;			
		if (reg.count == 0)
			continue;
		
		if (reg.connections.size() == 1 && reg.connections[0] == 0)
		{
			if (reg.count < minRegionSize)
			{
				// Non-connected small region, remove.
				reg.count = 0;
				reg.id = 0;
			}
		}
	}
		
		
	// Merge too small regions to neighbour regions.
	int mergeCount = 0 ;
	do
	{
		mergeCount = 0;
		for (int i = 0; i < nreg; ++i)
		{
			rcRegion& reg = regions[i];
			if (reg.id == 0 || (reg.id & RC_BORDER_REG))
				continue;			
			if (reg.count == 0)
				continue;
				
			// Check to see if the region should be merged.
			if (reg.count > mergeRegionSize && isRegionConnectedToBorder(reg))
				continue;
				
			// Small region with more than 1 connection.
			// Or region which is not connected to a border at all.
			// Find smallest neighbour region that connects to this one.
			int smallest = 0xfffffff;
			unsigned short mergeId = reg.id;
			for (int j = 0; j < reg.connections.size(); ++j)
			{
				if (reg.connections[j] & RC_BORDER_REG) continue;
				rcRegion& mreg = regions[reg.connections[j]];
				if (mreg.id == 0 || (mreg.id & RC_BORDER_REG)) continue;
				if (mreg.count < smallest &&
					canMergeWithRegion(reg, mreg.id) &&
					canMergeWithRegion(mreg, reg.id))
				{
					smallest = mreg.count;
					mergeId = mreg.id;
				}
			}
			// Found new id.
			if (mergeId != reg.id)
			{
				unsigned short oldId = reg.id;
				rcRegion& target = regions[mergeId];
				
				// Merge neighbours.
				if (mergeRegions(target, reg))
				{
					// Fixup regions pointing to current region.
					for (int j = 0; j < nreg; ++j)
					{
						if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG)) continue;
						// If another region was already merged into current region
						// change the nid of the previous region too.
						if (regions[j].id == oldId)
							regions[j].id = mergeId;
						// Replace the current region with the new one if the
						// current regions is neighbour.
						replaceNeighbour(regions[j], oldId, mergeId);
					}
					mergeCount++;
				}
			}
		}
	}
	while (mergeCount > 0);

	// Compress region Ids.
	for (int i = 0; i < nreg; ++i)
	{
		regions[i].remap = false;
		if (regions[i].id == 0) continue;	// Skip nil regions.
		if (regions[i].id & RC_BORDER_REG) continue;	// Skip external regions.
		regions[i].remap = true;
	}

	unsigned short regIdGen = 0;
	for (int i = 0; i < nreg; ++i)
	{
		if (!regions[i].remap)
			continue;
		unsigned short oldId = regions[i].id;
		unsigned short newId = ++regIdGen;
		for (int j = i; j < nreg; ++j)
		{
			if (regions[j].id == oldId)
			{
				regions[j].id = newId;
				regions[j].remap = false;
			}
		}
	}
	maxRegionId = regIdGen;
		
	// Remap regions.
	for (int i = 0; i < chf.spanCount; ++i)
	{
		if ((src[i*2] & RC_BORDER_REG) == 0)
			src[i*2] = regions[src[i*2]].id;
	}
	
	delete [] regions;
	
	return true;
}
Exemplo n.º 2
0
static bool filterSmallRegions(rcContext* ctx, int minRegionArea, int mergeRegionSize,
                               unsigned short& maxRegionId,
                               rcCompactHeightfield& chf,
                               unsigned short* srcReg)
{
    const int w = chf.width;
    const int h = chf.height;
    
    const int nreg = maxRegionId+1;
    rcRegion* regions = (rcRegion*)rcAlloc(sizeof(rcRegion)*nreg, RC_ALLOC_TEMP);
    if (!regions)
    {
        ctx->log(RC_LOG_ERROR, "filterSmallRegions: Out of memory 'regions' (%d).", nreg);
        return false;
    }

    // Construct regions
    for (int i = 0; i < nreg; ++i)
        new(&regions[i]) rcRegion((unsigned short)i);
    
    // Find edge of a region and find connections around the contour.
    for (int y = 0; y < h; ++y)
    {
        for (int x = 0; x < w; ++x)
        {
            const rcCompactCell& c = chf.cells[x+y*w];
            for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i)
            {
                unsigned short r = srcReg[i];
                if (r == 0 || r >= nreg)
                    continue;
                
                rcRegion& reg = regions[r];
                reg.spanCount++;
                
                
                // Update floors.
                for (int j = (int)c.index; j < ni; ++j)
                {
                    if (i == j) continue;
                    unsigned short floorId = srcReg[j];
                    if (floorId == 0 || floorId >= nreg)
                        continue;
                    addUniqueFloorRegion(reg, floorId);
                }
                
                // Have found contour
                if (reg.connections.size() > 0)
                    continue;
                
                reg.areaType = chf.areas[i];
                
                // Check if this cell is next to a border.
                int ndir = -1;
                for (int dir = 0; dir < 4; ++dir)
                {
                    if (isSolidEdge(chf, srcReg, x, y, i, dir))
                    {
                        ndir = dir;
                        break;
                    }
                }
                
                if (ndir != -1)
                {
                    // The cell is at border.
                    // Walk around the contour to find all the neighbours.
                    walkContour(x, y, i, ndir, chf, srcReg, reg.connections);
                }
            }
        }
    }

    // Remove too small regions.
    rcIntArray stack(32);
    rcIntArray trace(32);
    for (int i = 0; i < nreg; ++i)
    {
        rcRegion& reg = regions[i];
        if (reg.id == 0 || (reg.id & RC_BORDER_REG))
            continue;                       
        if (reg.spanCount == 0)
            continue;
        if (reg.visited)
            continue;
        
        // Count the total size of all the connected regions.
        // Also keep track of the regions connects to a tile border.
        bool connectsToBorder = false;
        int spanCount = 0;
        stack.resize(0);
        trace.resize(0);

        reg.visited = true;
        stack.push(i);
        
        while (stack.size())
        {
            // Pop
            int ri = stack.pop();
            
            rcRegion& creg = regions[ri];

            spanCount += creg.spanCount;
            trace.push(ri);

            for (int j = 0; j < creg.connections.size(); ++j)
            {
                if (creg.connections[j] & RC_BORDER_REG)
                {
                    connectsToBorder = true;
                    continue;
                }
                rcRegion& neireg = regions[creg.connections[j]];
                if (neireg.visited)
                    continue;
                if (neireg.id == 0 || (neireg.id & RC_BORDER_REG))
                    continue;
                // Visit
                stack.push(neireg.id);
                neireg.visited = true;
            }
        }
        
        // If the accumulated regions size is too small, remove it.
        // Do not remove areas which connect to tile borders
        // as their size cannot be estimated correctly and removing them
        // can potentially remove necessary areas.
        if (spanCount < minRegionArea && !connectsToBorder)
        {
            // Kill all visited regions.
            for (int j = 0; j < trace.size(); ++j)
            {
                regions[trace[j]].spanCount = 0;
                regions[trace[j]].id = 0;
            }
        }
    }
        
    // Merge too small regions to neighbour regions.
    int mergeCount = 0 ;
    do
    {
        mergeCount = 0;
        for (int i = 0; i < nreg; ++i)
        {
            rcRegion& reg = regions[i];
            if (reg.id == 0 || (reg.id & RC_BORDER_REG))
                continue;                       
            if (reg.spanCount == 0)
                continue;
            
            // Check to see if the region should be merged.
            if (reg.spanCount > mergeRegionSize && isRegionConnectedToBorder(reg))
                continue;
            
            // Small region with more than 1 connection.
            // Or region which is not connected to a border at all.
            // Find smallest neighbour region that connects to this one.
            int smallest = 0xfffffff;
            unsigned short mergeId = reg.id;
            for (int j = 0; j < reg.connections.size(); ++j)
            {
                if (reg.connections[j] & RC_BORDER_REG) continue;
                rcRegion& mreg = regions[reg.connections[j]];
                if (mreg.id == 0 || (mreg.id & RC_BORDER_REG)) continue;
                if (mreg.spanCount < smallest &&
                    canMergeWithRegion(reg, mreg) &&
                    canMergeWithRegion(mreg, reg))
                {
                    smallest = mreg.spanCount;
                    mergeId = mreg.id;
                }
            }
            // Found new id.
            if (mergeId != reg.id)
            {
                unsigned short oldId = reg.id;
                rcRegion& target = regions[mergeId];
                
                // Merge neighbours.
                if (mergeRegions(target, reg))
                {
                    // Fixup regions pointing to current region.
                    for (int j = 0; j < nreg; ++j)
                    {
                        if (regions[j].id == 0 || (regions[j].id & RC_BORDER_REG)) continue;
                        // If another region was already merged into current region
                        // change the nid of the previous region too.
                        if (regions[j].id == oldId)
                            regions[j].id = mergeId;
                        // Replace the current region with the new one if the
                        // current regions is neighbour.
                        replaceNeighbour(regions[j], oldId, mergeId);
                    }
                    mergeCount++;
                }
            }
        }
    }
    while (mergeCount > 0);
    
    // Compress region Ids.
    for (int i = 0; i < nreg; ++i)
    {
        regions[i].remap = false;
        if (regions[i].id == 0) continue;       // Skip nil regions.
        if (regions[i].id & RC_BORDER_REG) continue;    // Skip external regions.
        regions[i].remap = true;
    }
    
    unsigned short regIdGen = 0;
    for (int i = 0; i < nreg; ++i)
    {
        if (!regions[i].remap)
            continue;
        unsigned short oldId = regions[i].id;
        unsigned short newId = ++regIdGen;
        for (int j = i; j < nreg; ++j)
        {
            if (regions[j].id == oldId)
            {
                regions[j].id = newId;
                regions[j].remap = false;
            }
        }
    }
    maxRegionId = regIdGen;
    
    // Remap regions.
    for (int i = 0; i < chf.spanCount; ++i)
    {
        if ((srcReg[i] & RC_BORDER_REG) == 0)
            srcReg[i] = regions[srcReg[i]].id;
    }
    
    for (int i = 0; i < nreg; ++i)
        regions[i].~rcRegion();
    rcFree(regions);
    
    return true;
}
static dtStatus filterSmallRegions(dtTileCacheAlloc* alloc, dtTileCacheLayer& layer, int minRegionArea, int mergeRegionSize,
                                   unsigned short& maxRegionId, unsigned short* srcReg)
{
    const int w = (int)layer.header->width;
    const int h = (int)layer.header->height;

    const int nreg = maxRegionId+1;
    dtFixedArray<dtLayerRegion> regions(alloc, nreg);
    if (!regions)
    {
        return DT_FAILURE | DT_OUT_OF_MEMORY;
    }

    // Construct regions
    regions.set(0);
    for (int i = 0; i < nreg; ++i)
        regions[i] = dtLayerRegion((unsigned short)i);

    // Find edge of a region and find connections around the contour.
    for (int y = 0; y < h; ++y)
    {
        for (int x = 0; x < w; ++x)
        {
            const int i = x+y*w;
            unsigned short r = srcReg[i];

            if (r == DT_TILECACHE_NULL_AREA || r >= nreg)
                continue;

            dtLayerRegion& reg = regions[r];
            reg.cellCount++;

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

            reg.areaType = layer.areas[i];

            // Check if this cell is next to a border.
            int ndir = -1;
            for (int dir = 0; dir < 4; ++dir)
            {
                if (isSolidEdge(layer, srcReg, x, y, i, dir))
                {
                    ndir = dir;
                    break;
                }
            }

            if (ndir != -1)
            {
                // The cell is at border.
                // Walk around the contour to find all the neighbours.
                walkContour(x, y, i, ndir, layer, srcReg, reg.connections);
            }
        }
    }

    // Remove too small regions.
    dtIntArray stack(32);
    dtIntArray trace(32);
    for (int i = 0; i < nreg; ++i)
    {
        dtLayerRegion& reg = regions[i];
        if (reg.id == 0 || (reg.id & DT_BORDER_REG))
            continue;
        if (reg.cellCount == 0)
            continue;
        if (reg.visited)
            continue;

        // Count the total size of all the connected regions.
        // Also keep track of the regions connects to a tile border.
        bool connectsToBorder = false;
        int cellCount = 0;
        stack.resize(0);
        trace.resize(0);

        reg.visited = true;
        stack.push(i);

        while (stack.size())
        {
            // Pop
            int ri = stack.pop();

            dtLayerRegion& creg = regions[ri];

            cellCount += creg.cellCount;
            trace.push(ri);

            for (int j = 0; j < creg.connections.size(); ++j)
            {
                if (creg.connections[j] & DT_BORDER_REG)
                {
                    connectsToBorder = true;
                    continue;
                }
                dtLayerRegion& neireg = regions[creg.connections[j]];
                if (neireg.visited)
                    continue;
                if (neireg.id == 0 || (neireg.id & DT_BORDER_REG))
                    continue;
                // Visit
                stack.push(neireg.id);
                neireg.visited = true;
            }
        }

        // If the accumulated regions size is too small, remove it.
        // Do not remove areas which connect to tile borders
        // as their size cannot be estimated correctly and removing them
        // can potentially remove necessary areas.
        if (cellCount < minRegionArea && !connectsToBorder)
        {
            // Kill all visited regions.
            for (int j = 0; j < trace.size(); ++j)
            {
                regions[trace[j]].cellCount = 0;
                regions[trace[j]].id = 0;
            }
        }
    }

    // Merge too small regions to neighbour regions.
    int mergeCount = 0 ;
    do
    {
        mergeCount = 0;
        for (int i = 0; i < nreg; ++i)
        {
            dtLayerRegion& reg = regions[i];
            if (reg.id == 0 || (reg.id & DT_BORDER_REG))
                continue;
            if (reg.cellCount == 0)
                continue;

            // Check to see if the region should be merged.
            if (reg.cellCount > mergeRegionSize && isRegionConnectedToBorder(reg))
                continue;

            // Small region with more than 1 connection.
            // Or region which is not connected to a border at all.
            // Find smallest neighbour region that connects to this one.
            int smallest = 0xfffffff;
            unsigned short mergeId = reg.id;
            for (int j = 0; j < reg.connections.size(); ++j)
            {
                if (reg.connections[j] & DT_BORDER_REG) continue;
                dtLayerRegion& mreg = regions[reg.connections[j]];
                if (mreg.id == 0 || (mreg.id & DT_BORDER_REG)) continue;
                if (mreg.cellCount < smallest &&
                        canMergeWithRegion(reg, mreg) &&
                        canMergeWithRegion(mreg, reg))
                {
                    smallest = mreg.cellCount;
                    mergeId = mreg.id;
                }
            }
            // Found new id.
            if (mergeId != reg.id)
            {
                unsigned short oldId = reg.id;
                dtLayerRegion& target = regions[mergeId];

                // Merge neighbours.
                if (mergeRegions(target, reg))
                {
                    // Fixup regions pointing to current region.
                    for (int j = 0; j < nreg; ++j)
                    {
                        if (regions[j].id == 0 || (regions[j].id & DT_BORDER_REG)) continue;
                        // If another region was already merged into current region
                        // change the nid of the previous region too.
                        if (regions[j].id == oldId)
                            regions[j].id = mergeId;
                        // Replace the current region with the new one if the
                        // current regions is neighbour.
                        replaceNeighbour(regions[j], oldId, mergeId);
                    }
                    mergeCount++;
                }
            }
        }
    }
    while (mergeCount > 0);

    // Compress region Ids.
    for (int i = 0; i < nreg; ++i)
    {
        regions[i].remap = false;
        if (regions[i].id == DT_TILECACHE_NULL_AREA) continue;       // Skip nil regions.
        if (regions[i].id & DT_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 = w*h-1; i >= 0; i--)
    {
        if ((srcReg[i] & DT_BORDER_REG) == 0)
            srcReg[i] = regions[srcReg[i]].id;
    }

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

    return DT_SUCCESS;
}