/// <summary> /// Draw the fringe tiles such as grass and rocks. /// </summary> /// <param name="spriteBatch">The sprite batch that is being drawn.</param> /// <param name="material">The terrain material.</param> /// <param name="terrainBounds">The bounds of the terrain block in terrain units.</param> /// <param name="groundNodes">The ground nodes which represents the walkable ground in the terrain on which the /// fringe sprites will be rendered.</param> private void DrawTerrainFringe( SpriteBatch spriteBatch, TerrainMaterial material, Square terrainBounds, Dictionary<Point, LinkedPathNode> groundNodes) { // Determine which segments (if any) of this terrain block are ground nodes var groundPoints = new List<Tuple<int, int>>(); int groundY = terrainBounds.Y - 1; int currentStart = -1; int currentLength = 0; for (int x = terrainBounds.X; x < terrainBounds.Right; x++) { // Check if this point is a ground node if (groundNodes.ContainsKey(new Point(x, groundY))) { // This is a ground node, so add it to the current point set if (currentStart != -1) { currentLength++; } else { currentStart = x; currentLength = 1; } } else { // This is not a ground node. If a range was being build, add it to the collection if (currentStart != -1) { groundPoints.Add(Tuple.Create(currentStart, currentLength)); currentStart = -1; } } } // Add the last x range if (currentStart != -1) { groundPoints.Add(Tuple.Create(currentStart, currentLength)); currentStart = -1; } // If there are no ground points for this terrain block, there is nothing to render if (groundPoints.Count == 0) { return; } // Get the variations of the lower fringe sprites // TODO: Use the TerrainMaterial value, rather than just using mud here var lowerFringeRects = new Dictionary<int, Rectangle>(); foreach (int variation in this.resources.GetSpriteVariations("terrain", "lowerfringe", "mud")) { lowerFringeRects.Add(variation, this.resources.GetSpriteRectangle("terrain", "lowerfringe", "mud", variation)); } // Get the variations of the upper fringe sprites // TODO: Use the TerrainMaterial value, rather than just using mud here var upperFringeRects = new Dictionary<int, Rectangle>(); foreach (int variation in this.resources.GetSpriteVariations("terrain", "upperfringe", "mud")) { upperFringeRects.Add(variation, this.resources.GetSpriteRectangle("terrain", "upperfringe", "mud", variation)); } // Do nothing if no sprites exist for this terrain material if (lowerFringeRects.Count == 0 && upperFringeRects.Count == 0) { return; } // Determine the offset of the left side of the tile int offsetX = terrainBounds.X % Const.TileSize; // Draw the sprites foreach (Tuple<int, int> range in groundPoints) { int startX = range.Item1; int width = range.Item2; for (int x = startX; x < startX + width; x += Const.TileSize) { // Calculate the x position of the tile int tileX = x - offsetX; // Get a random sprite seeded by the x/y tile coordinate int lowerFringeIndex = new Random(tileX ^ terrainBounds.Y).Next(0, lowerFringeRects.Count); Rectangle lowerFringeRect = lowerFringeRects.ElementAt(lowerFringeIndex).Value; int upperFringeIndex = new Random(tileX ^ terrainBounds.Y).Next(0, upperFringeRects.Count); Rectangle upperFringeRect = upperFringeRects.ElementAt(upperFringeIndex).Value; // Clip the left bounds if (tileX < startX) { int diff = startX - tileX; tileX += diff; lowerFringeRect.X += diff; lowerFringeRect.Width -= diff; upperFringeRect.X += diff; upperFringeRect.Width -= diff; } // Clip the right bounds if (x + lowerFringeRect.Width > startX + width) { int newWidth = startX + width - x; lowerFringeRect.Width = newWidth; upperFringeRect.Width = newWidth; } // Create the dest rectangles var lowerFringeDestRect = new Rectangle(tileX, terrainBounds.Y, lowerFringeRect.Width, lowerFringeRect.Height); var upperFringeDestRect = new Rectangle( tileX, terrainBounds.Y - upperFringeRect.Height, // Upper fringe is above ground, so offset by its height upperFringeRect.Width, upperFringeRect.Height); // Draw the sprites spriteBatch.Draw(this.resources.SpriteSheet, lowerFringeDestRect, lowerFringeRect, Color.White); spriteBatch.Draw(this.resources.SpriteSheet, upperFringeDestRect, upperFringeRect, Color.White); } } }
/// <summary> /// Determine whether this square contains the given square. /// </summary> /// <param name="other">The square to test.</param> /// <returns>True if this square contains the given square.</returns> public bool Contains(Square other) { return other.Top >= this.Top && other.Left >= this.Left && other.Bottom <= this.Bottom && other.Right <= this.Right; }
/// <summary> /// Determine whether this square intersects the given square. /// </summary> /// <param name="other">The square to test.</param> /// <returns>True if this square intersects the given square.</returns> public bool Intersects(Square other) { return !(this.Bottom <= other.Top || this.Top >= other.Bottom || this.Right <= other.Left || this.Left >= other.Right); }
/// <summary> /// Populate the quad tree quadrants in the given bounds from the bit map data. /// <para /> /// This function will be recursively called the on the four sub-quadrants of this quadrant. If all of the sub- /// quandrants share the same material, then no data is added to the quad tree and the material is returned. /// The data will be set at a higher level in the quad tree hierarchy. The reason for this is because if the /// parent quadrant is also fully uniform, then any quadrants below it (this one) would have be removed, /// wasting processor time. /// <para /> /// If any of the sub-quadrants are of a fully uniform material (yet not *all* sub-quadrants like in the above /// case) then the data for those sub-quadrants is set in the quad tree here, making those sub-quadrants leaf /// nodes (an example of this could be Q1 and Q2 returning TerrainMaterial.None, Q3 returning /// TerrainMaterial.Mud, and Q4 returning null since it is a 'mixed' quadrant and the data is set at a lower /// level. In this situation Q1, Q2 and Q3 would have their data set at this level). /// </summary> /// <param name="quadTree">The quad tree to populate.</param> /// <param name="bounds">The bounds of the quadrant whose sub-quandrants are being populated.</param> /// <param name="bitmapData">The bit map color data.</param> /// <param name="bitmapWidth">The width in pixels of the bitmap image.</param> /// <param name="currentTime">The current time used as the terrain creation time.</param> /// <returns>The material value if all sub-quadrants in this bound are filled with the same material; Null if /// the sub-quadrants have various materials.</returns> private TerrainMaterial? PopulateQuadrants( ClipQuadTree<TerrainData> quadTree, Square bounds, Color[] bitmapData, int bitmapWidth, TimeSpan currentTime) { if (bounds.Length > 1) { // Get the sub-quadrant bounds of this quadrant Square topLeftBounds = bounds.GetTopLeftQuadrant(); Square topRightBounds = bounds.GetTopRightQuadrant(); Square bottomLeftBounds = bounds.GetBottomLeftQuadrant(); Square bottomRightBounds = bounds.GetBottomRightQuadrant(); // Populate the sub quadrants or if they are uniform get the single material they contain TerrainMaterial? topLeftTerrain = this.PopulateQuadrants(quadTree, topLeftBounds, bitmapData, bitmapWidth, currentTime); TerrainMaterial? topRightTerrain = this.PopulateQuadrants(quadTree, topRightBounds, bitmapData, bitmapWidth, currentTime); TerrainMaterial? bottomLeftTerrain = this.PopulateQuadrants(quadTree, bottomLeftBounds, bitmapData, bitmapWidth, currentTime); TerrainMaterial? bottomRightTerrain = this.PopulateQuadrants(quadTree, bottomRightBounds, bitmapData, bitmapWidth, currentTime); // Check if all of the quadrants are uniform and all have the same material if (topLeftTerrain.HasValue && topLeftTerrain == topRightTerrain && topRightTerrain == bottomLeftTerrain && bottomLeftTerrain == bottomRightTerrain) { // All quadrants have the same material. Return this to create the quadrant at a higher level return topLeftTerrain; } else { // The quadrants do not all share the same material. However, they may themselves be uniform in // which case their quadrant values need to be set in the quad tree if (topLeftTerrain.HasValue) { // Quadrant has a uniform color quadTree.SetData( new TerrainData(topLeftTerrain.Value, currentTime), topLeftBounds, null); } if (topRightTerrain.HasValue) { // Quadrant has a uniform color quadTree.SetData( new TerrainData(topRightTerrain.Value, currentTime), topRightBounds, null); } if (bottomLeftTerrain.HasValue) { // Quadrant has a uniform color quadTree.SetData( new TerrainData(bottomLeftTerrain.Value, currentTime), bottomLeftBounds, null); } if (bottomRightTerrain.HasValue) { // Quadrant has a uniform color quadTree.SetData( new TerrainData(bottomRightTerrain.Value, currentTime), bottomRightBounds, null); } // Return null since there isnt a single color shared between all the quadrants return null; } } else { // Get the terrain material at this pixel if (bounds.X < bitmapWidth) { int dataIndex = bounds.X + (bounds.Y * bitmapWidth); if (dataIndex < bitmapData.Length) { return TerrainMaterialConverter.GetValue(bitmapData[dataIndex]); } else { // The index is out of bounds for this quadrant. This will happen if the bitmap image is not a // square with a power-of-2 length. Just return TerrainMaterial.None return TerrainMaterial.None; } } else { // The index is out of bounds for this quadrant. This will happen if the bitmap image is not a // square with a power-of-2 length. Just return TerrainMaterial.None return TerrainMaterial.None; } } }