/// <summary> /// Recursive algorithm for finding all entraces/exits in a cluster, and creating GraphNodes at those points /// in neighbouring Clusters and linking them together. Has logic to only create on entrance/exit per concurent /// set of empty tiles. /// </summary> /// <param name="cluster">The cluster we are evaluating. Hitting a Tile outside this cluster will end that wall.</param> /// <param name="tile">The current tile to evaluate.</param> /// <param name="sequeceStart">The tile which started this sequence. Use null to start.</param> /// <param name="dirWalk">The direction to walk along the wall.</param> /// <param name="dirCheck">The direction of the neighbouring Cluster to check. It isn't enough to check outselves; the neighbour may be blocking travel.</param> /// <param name="dirNeighbourCluster">Same as dirCheck but using the enum that Clusters understand.</param> /// <returns>The last tile visited on this wall. Useful for walking an entire perimeter.</returns> private Level.Tile WalkWall(Cluster cluster, Level.Tile tile, Level.Tile sequeceStart, Level.Tile.AdjacentTileDir dirWalk, Level.Tile.AdjacentTileDir dirCheck, Cluster.AdjacentClusterDirections dirNeighbourCluster) { // Get the Tile in the neighbouring Cluster. It being solid creates a wall just the same as // if the tile in this Cluster is solid. Level.Tile adj = tile.mAdjecentTiles[(Int32)dirCheck]; Boolean entraceMade = false; if (null != adj) { // If we don't yet have a sequence start point, and this tile is an entrace, // it becomes the new sequence start. if (null == sequeceStart && tile.mType == Level.Tile.TileTypes.Empty && adj.mType == Level.Tile.TileTypes.Empty) { sequeceStart = tile; } // The sequence has started already and we just hit a wall. Time to create an entrance in the // center of this sequence. else if (null != sequeceStart && (tile.mType != Level.Tile.TileTypes.Empty || adj.mType != Level.Tile.TileTypes.Empty)) { CreateEntrance(cluster, tile, ref sequeceStart, dirWalk, dirCheck, dirNeighbourCluster, true); entraceMade = true; } } // Walk to the next Tile. adj = tile.mAdjecentTiles[(Int32)dirWalk]; // Are we still in the Cluster/Level? if (null != adj && cluster.IsInBounds(adj)) { // Recursivly visit the next Tile. return(WalkWall(cluster, adj, sequeceStart, dirWalk, dirCheck, dirNeighbourCluster)); } else { // We have left either the map or the Cluster. Either way that is considered an end to // the current sequence, should one be in progress. if (null != sequeceStart) { System.Diagnostics.Debug.Assert(!entraceMade, "Entrance made twice."); CreateEntrance(cluster, tile, ref sequeceStart, dirWalk, dirCheck, dirNeighbourCluster, false); } return(tile); } }
/// <summary> /// Helper function to do the work needed to create a single new extrance/exit based on the current tile being walked, /// and the Tile at the start of the sequence. /// </summary> /// <param name="cluster">The cluster we are evaluating. Hitting a Tile outside this cluster will end that wall.</param> /// <param name="tile">The current tile to evaluate.</param> /// <param name="sequeceStart">The tile which started this sequence. Use null to start.</param> /// <param name="dirWalk">The direction to walk along the wall.</param> /// <param name="dirCheck">The direction of the neighbouring Cluster to check. It isn't enough to check outselves; the neighbour may be blocking travel.</param> /// <param name="dirNeighbourCluster">Same as dirCheck but using the enum that Clusters understand.</param> /// <param name="removeSelf">True if the tile that triggered this call should not be included in the sequence. Useful if this sequence ended because you hit a wall.</param> private void CreateEntrance(Cluster cluster, Level.Tile tile, ref Level.Tile sequenceStart, Level.Tile.AdjacentTileDir dirWalk, Level.Tile.AdjacentTileDir dirCheck, Cluster.AdjacentClusterDirections dirNeighbourCluster, Boolean removeSelf) { // Find the center point between the tile at the start of the sequence of enpty tiles // and the current tile. Vector2 sequenceVector = tile.mCollisionRect.pCenterPoint - sequenceStart.mCollisionRect.pCenterPoint; // If we enter this block by hitting a wall, we need to remove that a Tile length from our // calculations since that wall is not part of the entrance/exit. if (removeSelf) { sequenceVector -= Vector2.Normalize(sequenceVector) * new Vector2(cluster.pTileDimensions.X, cluster.pTileDimensions.Y); } // If the sequence is long enough, instead of putting a GraphNode in the center, create 2 GraphNode objects, // and place them at opposite ends of the Sequence. This is recommended by the original HPA* white paper. if (sequenceVector.LengthSquared() >= (mClusterSize * mGetMapInfoMsg.mInfo_Out.mMapWidth * 0.5f)) { // Add the length of the Sequence to the starting point to get our ending position. Vector2 end = (sequenceVector) + sequenceStart.mCollisionRect.pCenterPoint; // We need to find the tile at that position because our GraphNode depends on that data. mGetTileAtPositionMsg.mPosition_In = end; WorldManager.pInstance.pCurrentLevel.OnMessage(mGetTileAtPositionMsg); System.Diagnostics.Debug.Assert(null != mGetTileAtPositionMsg.mTile_Out, "Unable to find tile."); CreateEntranceNodes( cluster, cluster.pNeighbouringClusters[(Int32)dirNeighbourCluster], mGetTileAtPositionMsg.mTile_Out, mGetTileAtPositionMsg.mTile_Out.mAdjecentTiles[(Int32)dirCheck]); CreateEntranceNodes( cluster, cluster.pNeighbouringClusters[(Int32)dirNeighbourCluster], sequenceStart, sequenceStart.mAdjecentTiles[(Int32)dirCheck]); } else { // Add half the length in order to put us in the middle of the sequence. Vector2 middle = (sequenceVector * 0.5f) + sequenceStart.mCollisionRect.pCenterPoint; // We need to find the tile at that position because our GraphNode depends on that data. mGetTileAtPositionMsg.mPosition_In = middle; WorldManager.pInstance.pCurrentLevel.OnMessage(mGetTileAtPositionMsg); System.Diagnostics.Debug.Assert(null != mGetTileAtPositionMsg.mTile_Out, "Unable to find tile."); CreateEntranceNodes( cluster, cluster.pNeighbouringClusters[(Int32)dirNeighbourCluster], mGetTileAtPositionMsg.mTile_Out, mGetTileAtPositionMsg.mTile_Out.mAdjecentTiles[(Int32)dirCheck]); } // Start a new sequence. sequenceStart = null; }
/// <summary> /// Constrctor. /// </summary> /// <param name="solid">The direction of the solid tile relative to the tile that would be a doorway.</param> /// <param name="empty">The direction of the emoty tile relative to teh tile that would be a doorway.</param> public DoorwayConfiguration(Level.Tile.AdjacentTileDir solid, Level.Tile.AdjacentTileDir empty) { mSolid = solid; mEmpty = empty; }