예제 #1
0
void CCmpPathfinder::TerrainUpdateHelper(bool expandPassability)
{
	PROFILE3("TerrainUpdateHelper");

	CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
	CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
	CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
	CTerrain& terrain = GetSimContext().GetTerrain();

	if (!cmpTerrain || !cmpObstructionManager)
		return;

	u16 terrainSize = cmpTerrain->GetTilesPerSide();
	if (terrainSize == 0)
		return;

	if (!m_TerrainOnlyGrid || m_MapSize != terrainSize)
	{
		m_MapSize = terrainSize;

		SAFE_DELETE(m_TerrainOnlyGrid);
		m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE);
	}

	Grid<u16> shoreGrid = ComputeShoreGrid();

	// Compute initial terrain-dependent passability
	for (int j = 0; j < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++j)
	{
		for (int i = 0; i < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++i)
		{
			// World-space coordinates for this navcell
			fixed x, z;
			Pathfinding::NavcellCenter(i, j, x, z);

			// Terrain-tile coordinates for this navcell
			int itile = i / Pathfinding::NAVCELLS_PER_TILE;
			int jtile = j / Pathfinding::NAVCELLS_PER_TILE;

			// Gather all the data potentially needed to determine passability:

			fixed height = terrain.GetExactGroundLevelFixed(x, z);

			fixed water;
			if (cmpWaterManager)
				water = cmpWaterManager->GetWaterLevel(x, z);

			fixed depth = water - height;

			// Exact slopes give kind of weird output, so just use rough tile-based slopes
			fixed slope = terrain.GetSlopeFixed(itile, jtile);

			// Get world-space coordinates from shoreGrid (which uses terrain tiles)
			fixed shoredist = fixed::FromInt(shoreGrid.get(itile, jtile)).MultiplyClamp(TERRAIN_TILE_SIZE);

			// Compute the passability for every class for this cell
			NavcellData t = 0;
			for (PathfinderPassability& passability : m_PassClasses)
				if (!passability.IsPassable(depth, slope, shoredist))
					t |= passability.m_Mask;

			m_TerrainOnlyGrid->set(i, j, t);
		}
	}

	// Compute off-world passability
	// WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this
	const int edgeSize = 3 * Pathfinding::NAVCELLS_PER_TILE; // number of tiles around the edge that will be off-world

	NavcellData edgeMask = 0;
	for (PathfinderPassability& passability : m_PassClasses)
		edgeMask |= passability.m_Mask;

	int w = m_TerrainOnlyGrid->m_W;
	int h = m_TerrainOnlyGrid->m_H;

	if (cmpObstructionManager->GetPassabilityCircular())
	{
		for (int j = 0; j < h; ++j)
		{
			for (int i = 0; i < w; ++i)
			{
				// Based on CCmpRangeManager::LosIsOffWorld
				// but tweaked since it's tile-based instead.
				// (We double all the values so we can handle half-tile coordinates.)
				// This needs to be slightly tighter than the LOS circle,
				// else units might get themselves lost in the SoD around the edge.

				int dist2 = (i*2 + 1 - w)*(i*2 + 1 - w)
					+ (j*2 + 1 - h)*(j*2 + 1 - h);

				if (dist2 >= (w - 2*edgeSize) * (h - 2*edgeSize))
					m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
			}
		}
	}
	else
	{
		for (u16 j = 0; j < h; ++j)
			for (u16 i = 0; i < edgeSize; ++i)
				m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
		for (u16 j = 0; j < h; ++j)
			for (u16 i = w-edgeSize+1; i < w; ++i)
				m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
		for (u16 j = 0; j < edgeSize; ++j)
			for (u16 i = edgeSize; i < w-edgeSize+1; ++i)
				m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
		for (u16 j = h-edgeSize+1; j < h; ++j)
			for (u16 i = edgeSize; i < w-edgeSize+1; ++i)
				m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask);
	}

	if (!expandPassability)
		return;

	// Expand the impassability grid, for any class with non-zero clearance,
	// so that we can stop units getting too close to impassable navcells.
	// Note: It's not possible to perform this expansion once for all passabilities
	// with the same clearance, because the impassable cells are not necessarily the 
	// same for all these passabilities.
	for (PathfinderPassability& passability : m_PassClasses)
	{
		if (passability.m_Clearance == fixed::Zero())
			continue;

		int clearance = (passability.m_Clearance / Pathfinding::NAVCELL_SIZE).ToInt_RoundToInfinity();
		ExpandImpassableCells(*m_TerrainOnlyGrid, clearance, passability.m_Mask);
	}
}
예제 #2
0
void CCmpPathfinder::UpdateGrid()
{
	PROFILE3("UpdateGrid");

	CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
	if (!cmpTerrain)
		return; // error

	if (!m_PreserveUpdateInformations)
		m_ObstructionsDirty.Clean();
	else
		m_PreserveUpdateInformations = false; // Next time will be a regular update

	// If the terrain was resized then delete the old grid data
	if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide())
	{
		SAFE_DELETE(m_Grid);
		SAFE_DELETE(m_TerrainOnlyGrid);
	}

	// Initialise the terrain data when first needed
	if (!m_Grid)
	{
		m_MapSize = cmpTerrain->GetTilesPerSide();
		m_Grid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE);
		m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE);

		m_ObstructionsDirty.dirty = true;
		m_ObstructionsDirty.globallyDirty = true;
		m_ObstructionsDirty.globalRecompute = true;

		m_TerrainDirty = true;
	}

	CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
	cmpObstructionManager->UpdateInformations(m_ObstructionsDirty);

	if (!m_ObstructionsDirty.dirty && !m_TerrainDirty)
		return;

	// If the terrain has changed, recompute m_Grid
	// Else, use data from m_TerrainOnlyGrid and add obstructions
	if (m_TerrainDirty)
	{
		Grid<u16> shoreGrid = ComputeShoreGrid();

		ComputeTerrainPassabilityGrid(shoreGrid);

		// Compute off-world passability
		// WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this

		const int edgeSize = 3 * Pathfinding::NAVCELLS_PER_TILE; // number of tiles around the edge that will be off-world

		NavcellData edgeMask = 0;
		for (PathfinderPassability& passability : m_PassClasses)
			edgeMask |= passability.m_Mask;

		int w = m_Grid->m_W;
		int h = m_Grid->m_H;

		if (cmpObstructionManager->GetPassabilityCircular())
		{
			for (int j = 0; j < h; ++j)
			{
				for (int i = 0; i < w; ++i)
				{
					// Based on CCmpRangeManager::LosIsOffWorld
					// but tweaked since it's tile-based instead.
					// (We double all the values so we can handle half-tile coordinates.)
					// This needs to be slightly tighter than the LOS circle,
					// else units might get themselves lost in the SoD around the edge.

					int dist2 = (i*2 + 1 - w)*(i*2 + 1 - w)
						+ (j*2 + 1 - h)*(j*2 + 1 - h);

					if (dist2 >= (w - 2*edgeSize) * (h - 2*edgeSize))
						m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
				}
			}
		}
		else
		{
			for (u16 j = 0; j < h; ++j)
				for (u16 i = 0; i < edgeSize; ++i)
					m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
			for (u16 j = 0; j < h; ++j)
				for (u16 i = w-edgeSize+1; i < w; ++i)
					m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
			for (u16 j = 0; j < edgeSize; ++j)
				for (u16 i = edgeSize; i < w-edgeSize+1; ++i)
					m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
			for (u16 j = h-edgeSize+1; j < h; ++j)
				for (u16 i = edgeSize; i < w-edgeSize+1; ++i)
					m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
		}

		// Expand the impassability grid, for any class with non-zero clearance,
		// so that we can stop units getting too close to impassable navcells.
		// Note: It's not possible to perform this expansion once for all passabilities
		// with the same clearance, because the impassable cells are not necessarily the 
		// same for all these passabilities.
		for (PathfinderPassability& passability : m_PassClasses)
		{
			if (passability.m_Clearance == fixed::Zero())
				continue;

			int clearance = (passability.m_Clearance / Pathfinding::NAVCELL_SIZE).ToInt_RoundToInfinity();
			ExpandImpassableCells(*m_Grid, clearance, passability.m_Mask);
		}			

		// Store the updated terrain-only grid
		*m_TerrainOnlyGrid = *m_Grid;

		m_TerrainDirty = false;
		m_ObstructionsDirty.globalRecompute = true;
		m_ObstructionsDirty.globallyDirty = true;
	}
	else if (m_ObstructionsDirty.globalRecompute)
	{
		ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H);
		memcpy(m_Grid->m_Data, m_TerrainOnlyGrid->m_Data, (m_Grid->m_W)*(m_Grid->m_H)*sizeof(NavcellData));

		m_ObstructionsDirty.globallyDirty = true;
	}
	else
	{
		ENSURE(m_Grid->m_W == m_ObstructionsDirty.dirtinessGrid.m_W && m_Grid->m_H == m_ObstructionsDirty.dirtinessGrid.m_H);
		ENSURE(m_Grid->m_W == m_TerrainOnlyGrid->m_W && m_Grid->m_H == m_TerrainOnlyGrid->m_H);

		for (u16 i = 0; i < m_ObstructionsDirty.dirtinessGrid.m_W; ++i)
			for (u16 j = 0; j < m_ObstructionsDirty.dirtinessGrid.m_H; ++j)
				if (m_ObstructionsDirty.dirtinessGrid.get(i, j) == 1)
					m_Grid->set(i, j, m_TerrainOnlyGrid->get(i, j));
	}

	// Add obstructions onto the grid
	cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses, m_ObstructionsDirty.globalRecompute);

	// Update the long-range pathfinder
	if (m_ObstructionsDirty.globallyDirty)
		m_LongPathfinder.Reload(GetPathfindingPassabilityClasses(), m_Grid);
	else
		m_LongPathfinder.Update(m_Grid, m_ObstructionsDirty.dirtinessGrid);
}